diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f0d365af..8d9ccad2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,3 +30,15 @@ jobs: cache: 'yarn' - run: yarn install - run: yarn test + + test-webpack5-inline-match-resource: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set node version to 16 + uses: actions/setup-node@v2 + with: + node-version: 16 + cache: 'yarn' + - run: yarn install + - run: yarn test:match-resource diff --git a/README.md b/README.md index e47688d0..dd3d33e4 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,10 @@ - [Documentation](https://vue-loader.vuejs.org) +## v17.3+ Only Options + +- `experimentalInlineMatchResource: boolean`: enable [Inline matchResource](https://webpack.js.org/api/loaders/#inline-matchresource) for rule matching for vue-loader. + ## v16+ Only Options - `reactivityTransform: boolean`: enable [Vue Reactivity Transform](https://github.com/vuejs/rfcs/discussions/369) (SFCs only). diff --git a/jest.config.js b/jest.config.js index fc6e44be..4cc27660 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,4 +1,12 @@ -console.log(`running tests with webpack ${process.env.WEBPACK4 ? '4' : '5'}...`) +const isWebpack4 = process.env.WEBPACK4 + +console.log( + `running tests with webpack ${isWebpack4 ? '4' : '5'}${ + !isWebpack4 && process.env.INLINE_MATCH_RESOURCE + ? ' with inline match resource enabled' + : '' + }...`, +) module.exports = { preset: 'ts-jest', diff --git a/package.json b/package.json index 6b2594cb..b2fb9c9b 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,8 @@ "build": "tsc", "pretest": "tsc", "test": "jest", + "pretest:match-resource": "tsc", + "test:match-resource": "INLINE_MATCH_RESOURCE=true jest", "pretest:webpack4": "tsc", "test:webpack4": "WEBPACK4=true jest --forceExit", "dev-example": "node example/devServer.js --config example/webpack.config.js --inline --hot", diff --git a/src/index.ts b/src/index.ts index 55520bac..fb24e5f1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -22,7 +22,12 @@ import { formatError } from './formatError' import VueLoaderPlugin from './plugin' import { canInlineTemplate } from './resolveScript' import { setDescriptor } from './descriptorCache' -import { getOptions, stringifyRequest as _stringifyRequest } from './util' +import { + getOptions, + stringifyRequest as _stringifyRequest, + genMatchResource, + testWebpack5, +} from './util' export { VueLoaderPlugin } @@ -53,6 +58,7 @@ export interface VueLoaderOptions { exposeFilename?: boolean appendExtension?: boolean enableTsInTemplate?: boolean + experimentalInlineMatchResource?: boolean isServerBuild?: boolean } @@ -64,7 +70,7 @@ const exportHelperPath = require.resolve('./exportHelper') export default function loader( this: LoaderContext, - source: string + source: string, ) { const loaderContext = this @@ -77,8 +83,8 @@ export default function loader( loaderContext.emitError( new Error( `vue-loader was used without the corresponding plugin. ` + - `Make sure to include VueLoaderPlugin in your webpack config.` - ) + `Make sure to include VueLoaderPlugin in your webpack config.`, + ), ) errorEmitted = true } @@ -92,18 +98,23 @@ export default function loader( rootContext, resourcePath, resourceQuery: _resourceQuery = '', + _compiler, } = loaderContext + const isWebpack5 = testWebpack5(_compiler) const rawQuery = _resourceQuery.slice(1) const incomingQuery = qs.parse(rawQuery) const resourceQuery = rawQuery ? `&${rawQuery}` : '' const options = (getOptions(loaderContext) || {}) as VueLoaderOptions + const enableInlineMatchResource = + isWebpack5 && Boolean(options.experimentalInlineMatchResource) const isServer = options.isServerBuild ?? target === 'node' const isProduction = mode === 'production' || process.env.NODE_ENV === 'production' const filename = resourcePath.replace(/\?.*$/, '') + const { descriptor, errors } = parse(source, { filename, sourceMap, @@ -133,7 +144,7 @@ export default function loader( const id = hash( isProduction ? shortFilePath + '\n' + source.replace(/\r\n/g, '\n') - : shortFilePath + : shortFilePath, ) // if the query has a type field, this is a language block request @@ -146,7 +157,7 @@ export default function loader( options, loaderContext, incomingQuery, - !!options.appendExtension + !!options.appendExtension, ) } @@ -169,10 +180,23 @@ export default function loader( if (script || scriptSetup) { const lang = script?.lang || scriptSetup?.lang isTS = !!(lang && /tsx?/.test(lang)) + const externalQuery = Boolean(script && !scriptSetup && script.src) + ? `&external` + : `` const src = (script && !scriptSetup && script.src) || resourcePath const attrsQuery = attrsToQuery((scriptSetup || script)!.attrs, 'js') - const query = `?vue&type=script${attrsQuery}${resourceQuery}` - const scriptRequest = stringifyRequest(src + query) + const query = `?vue&type=script${attrsQuery}${resourceQuery}${externalQuery}` + + let scriptRequest: string + + if (enableInlineMatchResource) { + scriptRequest = stringifyRequest( + genMatchResource(this, src, query, lang || 'js'), + ) + } else { + scriptRequest = stringifyRequest(src + query) + } + scriptImport = `import script from ${scriptRequest}\n` + // support named exports @@ -186,13 +210,27 @@ export default function loader( const useInlineTemplate = canInlineTemplate(descriptor, isProduction) if (descriptor.template && !useInlineTemplate) { const src = descriptor.template.src || resourcePath + const externalQuery = Boolean(descriptor.template.src) ? `&external` : `` const idQuery = `&id=${id}` const scopedQuery = hasScoped ? `&scoped=true` : `` const attrsQuery = attrsToQuery(descriptor.template.attrs) const tsQuery = options.enableTsInTemplate !== false && isTS ? `&ts=true` : `` - const query = `?vue&type=template${idQuery}${scopedQuery}${tsQuery}${attrsQuery}${resourceQuery}` - templateRequest = stringifyRequest(src + query) + const query = `?vue&type=template${idQuery}${scopedQuery}${tsQuery}${attrsQuery}${resourceQuery}${externalQuery}` + + if (enableInlineMatchResource) { + templateRequest = stringifyRequest( + genMatchResource( + this, + src, + query, + options.enableTsInTemplate !== false && isTS ? 'ts' : 'js', + ), + ) + } else { + templateRequest = stringifyRequest(src + query) + } + templateImport = `import { ${renderFnName} } from ${templateRequest}` propsToAttach.push([renderFnName, renderFnName]) } @@ -209,19 +247,29 @@ export default function loader( .forEach((style, i) => { const src = style.src || resourcePath const attrsQuery = attrsToQuery(style.attrs, 'css') + const lang = String(style.attrs.lang || 'css') // make sure to only pass id when necessary so that we don't inject // duplicate tags when multiple components import the same css file const idQuery = !style.src || style.scoped ? `&id=${id}` : `` const inlineQuery = asCustomElement ? `&inline` : `` - const query = `?vue&type=style&index=${i}${idQuery}${inlineQuery}${attrsQuery}${resourceQuery}` - const styleRequest = stringifyRequest(src + query) + const externalQuery = Boolean(style.src) ? `&external` : `` + const query = `?vue&type=style&index=${i}${idQuery}${inlineQuery}${attrsQuery}${resourceQuery}${externalQuery}` + + let styleRequest + if (enableInlineMatchResource) { + styleRequest = stringifyRequest( + genMatchResource(this, src, query, lang), + ) + } else { + styleRequest = stringifyRequest(src + query) + } if (style.module) { if (asCustomElement) { loaderContext.emitError( new Error( - `