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

export * from './path' fails to load the requested module into moduleCache #168

Closed wduffy closed 5 months ago

wduffy commented 7 months ago

When a module being loaded includes an export * from './path' which has not already been imported to the moduleCache, the loader fails with error

require("./path") failed. module not found in moduleCache

To Reproduce To reproduce this behaviour import the following file tree within a .vue file import * as x from './src/a.mjs'

This will result in the error Error: require("PATH/B.mjs") failed. module not found in moduleCache

./src/a.mjs
export * from "./b.mjs"
export * from "./c.mjs"
export * from "./d.mjs"

./src/b.js
export function Wowza(name) {
    alert(`WOWZA, ${name}`)
}

./src/c.mjs
export { }

./src/d.mjs
const d = { letter: "D" }
export { d }

Expected behaviour I expect the loader to pre-empt the use of the export * from (b.mjs, c.mjs, d.mjs) modules exposed via './src/a.mjs' and resolve them to the moduleCache.

Versions

Additional context For now I manually pre-assess the response in getFile(path) to regex match any export from './path'. I then inject an import as x from './path' before the export. This resolves the problem (I believe because vue3-sfc-loader then pre-empts the usage of that module and resolves it to the moduleCache before proceeding). This is the code I use in getFile.

async getFile(url) {
    //console.log("getFile", url)

    url = /\.(js|mjs|css|less|vue)$/.test(url) ? url : `${url}.${url.startsWith(window.location.origin) ? "vue" : "js"}`
    const type = /.*?\.js|.mjs$/.test(url) ? ".mjs" : /.*?\.vue$/.test(url) ? '.vue' : /.*?\.css$/.test(url) ? '.css' : '.vue';

    const fetched = await fetch(url);
    if (!fetched.ok)
        throw Object.assign(new Error(`${fetched.statusText} ${url}`), { fetched });

    const getContentData = async asBinary => {
        let content = await (asBinary ? fetched.arrayBuffer() : fetched.text());

        // vue3-sfc-loader fails to import before attempting an [export * from "./path"] with ""
        // prefixing any with an import statement to populate the moduleCache
        if (!asBinary && content.includes("export * from")) {
            const matches = content.matchAll(/export \* from (['"])([^'"]+)['"];?/g)
            for (const match of matches) {
                var module = options.pathResolve({ refPath: url, relPath: match[2] })
                if (!options.moduleCache[module]) {
                    content = content.replace(match[0], `import * as vueLoaderExportSupport${match.index} from ${match[1]}${match[2]}${match[1]}\n${match[0]}\n`)
                }
            }
        }

        return content;
    }

    return { getContentData, type }
}

the above fix changes ./Src/a.mjs to the following, which stops the error occurring

import * as vueLoaderExportSupport1 from "./b.mjs"
export * from "./b.mjs"
import * as vueLoaderExportSupport2 from "./c.mjs"
export * from "./c.mjs"
import * as vueLoaderExportSupport3 from "./d.mjs"
export * from "./d.mjs"
FranckFreiburger commented 5 months ago

Hi wduffy, I've pinpointed the problem. This will be fixed in the next release.