Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support experimental inline match resource #2046

Merged
merged 3 commits into from
Jun 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@

- [Documentation](https://vue-loader.vuejs.org)

## v17.1+ 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).
Expand Down
10 changes: 9 additions & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
@@ -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',
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
"dev-example": "node example/devServer.js --config example/webpack.config.js --inline --hot",
Expand Down
85 changes: 76 additions & 9 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,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 }

Expand Down Expand Up @@ -51,6 +56,7 @@ export interface VueLoaderOptions {
exposeFilename?: boolean
appendExtension?: boolean
enableTsInTemplate?: boolean
experimentalInlineMatchResource?: boolean

isServerBuild?: boolean
}
Expand Down Expand Up @@ -90,18 +96,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,
Expand Down Expand Up @@ -167,10 +178,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
Expand All @@ -184,13 +208,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])
}
Expand All @@ -205,12 +243,23 @@ 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(
Expand Down Expand Up @@ -283,9 +332,27 @@ export default function loader(
const issuerQuery = block.attrs.src
? `&issuerPath=${qs.escape(resourcePath)}`
: ''
const query = `?vue&type=custom&index=${i}${blockTypeQuery}${issuerQuery}${attrsQuery}${resourceQuery}`

const externalQuery = Boolean(block.attrs.src) ? `&external` : ``
const query = `?vue&type=custom&index=${i}${blockTypeQuery}${issuerQuery}${attrsQuery}${resourceQuery}${externalQuery}`

let customRequest

if (enableInlineMatchResource) {
customRequest = stringifyRequest(
genMatchResource(
this,
src as string,
query,
block.attrs.lang as string
)
)
} else {
customRequest = stringifyRequest(src + query)
}

return (
`import block${i} from ${stringifyRequest(src + query)}\n` +
`import block${i} from ${customRequest}\n` +
`if (typeof block${i} === 'function') block${i}(script)`
)
})
Expand Down
66 changes: 61 additions & 5 deletions src/pitcher.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { LoaderDefinitionFunction, LoaderContext } from 'webpack'
import * as qs from 'querystring'
import { stringifyRequest } from './util'
import { getOptions, stringifyRequest, testWebpack5 } from './util'
import { VueLoaderOptions } from '.'

const selfPath = require.resolve('./index')
Expand Down Expand Up @@ -58,7 +58,40 @@ export const pitch = function () {
})

// Inject style-post-loader before css-loader for scoped CSS and trimming
const isWebpack5 = testWebpack5(context._compiler)
const options = (getOptions(context) || {}) as VueLoaderOptions
if (query.type === `style`) {
if (isWebpack5 && context._compiler?.options.experiments.css) {
// If user enables `experiments.css`, then we are trying to emit css code directly.
// Although we can target requests like `xxx.vue?type=style` to match `type: "css"`,
// it will make the plugin a mess.
if (!options.experimentalInlineMatchResource) {
context.emitError(
new Error(
'`experimentalInlineMatchResource` should be enabled if `experiments.css` enabled currently'
)
)
return ''
}

if (query.inline || query.module) {
context.emitError(
new Error(
'`inline` or `module` is currently not supported with `experiments.css` enabled'
)
)
return ''
}

const loaderString = [stylePostLoaderPath, ...loaders]
.map((loader) => {
return typeof loader === 'string' ? loader : loader.request
})
.join('!')
return `@import "${context.resourcePath}${
query.lang ? `.${query.lang}` : ''
}${context.resourceQuery}!=!-!${loaderString}!${context.resource}";`
}
const cssLoaderIndex = loaders.findIndex(isCSSLoader)
if (cssLoaderIndex > -1) {
// if inlined, ignore any loaders after css-loader and replace w/ inline
Expand All @@ -71,7 +104,8 @@ export const pitch = function () {
return genProxyModule(
[...afterLoaders, stylePostLoaderPath, ...beforeLoaders],
context,
!!query.module || query.inline != null
!!query.module || query.inline != null,
(query.lang as string) || 'css'
)
}
}
Expand All @@ -84,15 +118,21 @@ export const pitch = function () {

// Rewrite request. Technically this should only be done when we have deduped
// loaders. But somehow this is required for block source maps to work.
return genProxyModule(loaders, context, query.type !== 'template')
return genProxyModule(
loaders,
context,
query.type !== 'template',
query.ts ? 'ts' : (query.lang as string)
)
}

function genProxyModule(
loaders: (Loader | string)[],
context: LoaderContext<VueLoaderOptions>,
exportDefault = true
exportDefault = true,
lang = 'js'
) {
const request = genRequest(loaders, context)
const request = genRequest(loaders, lang, context)
// return a proxy module which simply re-exports everything from the
// actual request. Note for template blocks the compiled module has no
// default export.
Expand All @@ -104,12 +144,28 @@ function genProxyModule(

function genRequest(
loaders: (Loader | string)[],
lang: string,
context: LoaderContext<VueLoaderOptions>
) {
const isWebpack5 = testWebpack5(context._compiler)
const options = (getOptions(context) || {}) as VueLoaderOptions
const enableInlineMatchResource =
isWebpack5 && options.experimentalInlineMatchResource

const loaderStrings = loaders.map((loader) => {
return typeof loader === 'string' ? loader : loader.request
})
const resource = context.resourcePath + context.resourceQuery

if (enableInlineMatchResource) {
return stringifyRequest(
context,
`${context.resourcePath}${lang ? `.${lang}` : ''}${
context.resourceQuery
}!=!-!${[...loaderStrings, resource].join('!')}`
)
}

return stringifyRequest(
context,
'-!' + [...loaderStrings, resource].join('!')
Expand Down
23 changes: 15 additions & 8 deletions src/plugin.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
import webpack from 'webpack'
import type { Compiler } from 'webpack'
import { testWebpack5 } from './util'

declare class VueLoaderPlugin {
static NS: string
apply(compiler: Compiler): void
}

let Plugin: typeof VueLoaderPlugin
const NS = 'vue-loader'

if (webpack.version && webpack.version[0] > '4') {
// webpack5 and upper
Plugin = require('./pluginWebpack5').default
} else {
// webpack4 and lower
Plugin = require('./pluginWebpack4').default
class Plugin {
static NS = NS
apply(compiler: Compiler) {
let Ctor: typeof VueLoaderPlugin
if (testWebpack5(compiler)) {
// webpack5 and upper
Ctor = require('./pluginWebpack5').default
} else {
// webpack4 and lower
Ctor = require('./pluginWebpack4').default
}
new Ctor().apply(compiler)
}
}

export default Plugin
Loading