FranckFreiburger / vue3-sfc-loader

Single File Component loader for Vue2 and Vue3. Load .vue files directly from your HTML. No node.js environment, no build step.
MIT License
1.03k stars 116 forks source link

Technical Boss, How to use scss/sass? #149

Closed sudo349 closed 5 months ago

sudo349 commented 1 year ago

how to use scss/sass?

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

conanliuhuan commented 1 year ago

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/less@4.1.3/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'];
    //...
doubleedesign commented 5 months ago

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:

<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');
FranckFreiburger commented 5 months ago

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