vue3-sfc-loader icon indicating copy to clipboard operation
vue3-sfc-loader copied to clipboard

Technical Boss, How to use scss/sass?

Open sudo349 opened this issue 3 years ago • 2 comments
trafficstars

how to use scss/sass?

I looked at the example but didn't see how to use it

sudo349 avatar Oct 28 '22 09:10 sudo349

how to use scss/sass?

I looked at the example but didn't see how to use it

SASS我没有使用,但我使用了Less。原理一样,你可以参考一下:

  1. 在html中全局引入Less:<script src="https://unpkg.com/[email protected]/dist/less.min.js"></script>
  2. 配置vue3-sfc-loader的参数,addStyle时进行less转换:
const options = {
	moduleCache: {
		vue: Vue
	},
	async getFile(url) {
		const res = await fetch(url);
		if ( !res.ok )
			throw Object.assign(new Error(res.statusText + ' ' + url), { res });
		return {
			getContentData: asBinary => asBinary ? res.arrayBuffer() : res.text(),
		}
	},
	addStyle(textContent) {
		// 将less的字符串转换后再添加到style标签
		less.render(textContent, {}, (error, output)=>{
			if (error) {
				console.log("Less转换失败!")
			} else {
				const style = Object.assign(document.createElement('style'), { textContent: output.css });
				const ref = document.head.getElementsByTagName('style')[0] || null;
				document.head.insertBefore(style, ref);
			}
		})
	},
}
const { loadModule } = window['vue3-sfc-loader'];
//...

conanliuhuan avatar Dec 29 '22 05:12 conanliuhuan

I have worked out a way to do this, in particular including imports within the SCSS (e.g., to share variables). For context, I'm trying to use Vue for the output of content 'blocks' in a WordPress theme, so you'll see references to that scattered below but it's mostly file paths and naming conventions - this should be adaptable to a non-WordPress use case.

Important notes:

  • Scoping styles to the component using <style scoped> does not work. (It's something I would like to look into but isn't a high priority at the moment so do not have an ETA on a solution.)
  • You must use the snake-case syntax when you call the component in JSX, even if you define it in PascalCase as I have. I don't know why, but that's the only way it works.
<head>
<script src="https://cdn.jsdelivr.net/npm/vue3-sfc-loader/dist/vue3-sfc-loader.js"></script>
<script type="module" src="path/to/vue-blocks.js"></script>
</head>
<body>
<div id="app">
<?php
// this is a shortened version for clarity; 
// basically this is noting to load partials that are going to contain Vue components INSIDE #app
// If you aren't using PHP you would just put the Vue components straight in here
get_template_part('blocks/custom/call-to-action/index.php');
?>
</div>
</body>
// blocks/custom/call-to-action/index.php
// Example of a file where a component is called
// Props will also be passed into the `call-to-action` element when I complete it
// Must use snake-case for the component name
<div class="block__call-to-action">
    <call-to-action></call-to-action>
</div>
// call-to-action.vue
// The actual Vue single-file component
// This is incomplete in terms of props/dynamic data, but you'll get the idea
<script lang="ts">
export default {
	data() {
		return {
		};
	},
};
</script>

<template>
	<h2>Content to go here!</h2>
</template>

<style lang="scss">
@import '../../../scss/variables';
h2 {
	color: map-get($colours, 'primary');
}
</style>
// vue-blocks.js
// This is the file that creates the Vue app, imports the components, and compiles their SCSS
import * as Vue from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js';
import * as sass from 'https://jspm.dev/sass';

const siteUrl = window.location.origin;
const themeUrl = '/wp-content/themes/starterkit-blocks/';

const sassDepImporter = {
	load: async(url) => {
		const file = await fetch(url.pathname);
		const content = await file.text();
		return {
			contents: content,
			syntax: 'scss',
		};
	},
	canonicalize: (str) => {
		const fileTree = str.split('/').filter((part) => part !== '..');
		const fileName = `_${fileTree.pop()}.scss`;
		return new URL(`${fileTree}/${fileName}`, `${siteUrl}${themeUrl}`);
	},
};

const vueSfcLoaderOptions = {
	moduleCache: {
		vue: Vue,
		sass,
	},
	async getFile(url) {
		const res = await fetch(url);
		if (!res.ok) {
			throw Object.assign(new Error(res.statusText + ' ' + url), { res });
		}

		return {
			getContentData: () => {
				return res.text().then((content) => {
					// Filter out the <style> tags from the component as they need to be processed separately
					const dom = new DOMParser().parseFromString(content, 'text/html');
					return Array.from(dom.head.children)
						.filter((element) => element.tagName !== 'STYLE')
						.map((element) => element.outerHTML)
						.join('\n');
				});
			},
		};
	},
	async addStyle(fileUrl) {
		const res = await fetch(fileUrl);
		if (!res.ok) {
			throw Object.assign(new Error(res.statusText + ' ' + url), { res });
		}
		const dom = new DOMParser().parseFromString(await res.text(), 'text/html');
		const rawScss = Array.from(dom.head.children).find((element) => element.tagName === 'STYLE');
		const compiled = await sass.compileStringAsync(rawScss.textContent, {
			importers: [sassDepImporter],
		});

		const style = document.createElement('style');
		style.setAttribute('data-vue-component', fileUrl.split('/').pop());
		style.type = 'text/css';
		style.textContent = compiled.css;
		document.body.appendChild(style);
	},
	// eslint-disable-next-line no-shadow
	async handleModule(type, getContentData, path, options) {
		if (type === '.vue') {
			// Get and compile the SCSS from the component file
			options.addStyle(path);
		}

		//throw new Error(`Tried to import a non-vue file at ${path}`);
	},
};

const { loadModule } = window['vue3-sfc-loader'];

Vue.createApp({
	components: {
		CallToAction: Vue.defineAsyncComponent(() => loadModule(`${themeUrl}/blocks/custom/call-to-action/call-to-action.vue`, vueSfcLoaderOptions)),
		// Add more components here
	},
	template: '',
}).mount('#app');

doubleedesign avatar Jan 06 '24 05:01 doubleedesign

will be available in the next release. example: https://github.com/FranckFreiburger/vue3-sfc-loader/discussions/181

FranckFreiburger avatar Jan 23 '24 21:01 FranckFreiburger