Skip to content

Commit

Permalink
feat: use script-analyzed bindings when compiling template
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Jun 15, 2022
1 parent 52abb32 commit 55de28c
Show file tree
Hide file tree
Showing 14 changed files with 192 additions and 128 deletions.
6 changes: 5 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
module.exports = {
root: true,
extends: ['plugin:vue-libs/recommended']
extends: ['plugin:vue-libs/recommended'],
rules: {
indent: 'off',
'space-before-function-paren': 'off'
}
}
34 changes: 22 additions & 12 deletions lib/codegen/customBlocks.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,31 @@
const qs = require('querystring')
const { attrsToQuery } = require('./utils')

module.exports = function genCustomBlocksCode (
module.exports = function genCustomBlocksCode(
blocks,
resourcePath,
resourceQuery,
stringifyRequest
) {
return `\n/* custom blocks */\n` + blocks.map((block, i) => {
const src = block.attrs.src || resourcePath
const attrsQuery = attrsToQuery(block.attrs)
const issuerQuery = block.attrs.src ? `&issuerPath=${qs.escape(resourcePath)}` : ''
const inheritQuery = resourceQuery ? `&${resourceQuery.slice(1)}` : ''
const query = `?vue&type=custom&index=${i}&blockType=${qs.escape(block.type)}${issuerQuery}${attrsQuery}${inheritQuery}`
return (
`import block${i} from ${stringifyRequest(src + query)}\n` +
`if (typeof block${i} === 'function') block${i}(component)`
)
}).join(`\n`) + `\n`
return (
`\n/* custom blocks */\n` +
blocks
.map((block, i) => {
const src = block.attrs.src || resourcePath
const attrsQuery = attrsToQuery(block.attrs)
const issuerQuery = block.attrs.src
? `&issuerPath=${qs.escape(resourcePath)}`
: ''
const inheritQuery = resourceQuery ? `&${resourceQuery.slice(1)}` : ''
const query = `?vue&type=custom&index=${i}&blockType=${qs.escape(
block.type
)}${issuerQuery}${attrsQuery}${inheritQuery}`
return (
`import block${i} from ${stringifyRequest(src + query)}\n` +
`if (typeof block${i} === 'function') block${i}(component)`
)
})
.join(`\n`) +
`\n`
)
}
20 changes: 12 additions & 8 deletions lib/codegen/styleInjection.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const { attrsToQuery } = require('./utils')
const hotReloadAPIPath = JSON.stringify(require.resolve('vue-hot-reload-api'))
const nonWhitespaceRE = /\S+/

module.exports = function genStyleInjectionCode (
module.exports = function genStyleInjectionCode(
loaderContext,
styles,
id,
Expand All @@ -18,7 +18,7 @@ module.exports = function genStyleInjectionCode (
let hasCSSModules = false
const cssModuleNames = new Map()

function genStyleRequest (style, i) {
function genStyleRequest(style, i) {
const src = style.src || resourcePath
const attrsQuery = attrsToQuery(style.attrs, 'css')
const inheritQuery = `&${loaderContext.resourceQuery.slice(1)}`
Expand All @@ -29,7 +29,7 @@ module.exports = function genStyleInjectionCode (
return stringifyRequest(src + query)
}

function genCSSModulesCode (style, request, i) {
function genCSSModulesCode(style, request, i) {
hasCSSModules = true

const moduleName = style.module === true ? '$style' : style.module
Expand Down Expand Up @@ -71,7 +71,8 @@ module.exports = function genStyleInjectionCode (
}

// empty styles: with no `src` specified or only contains whitespaces
const isNotEmptyStyle = style => style.src || nonWhitespaceRE.test(style.content)
const isNotEmptyStyle = (style) =>
style.src || nonWhitespaceRE.test(style.content)
// explicit injection is needed in SSR (for critical CSS collection)
// or in Shadow Mode (for injection into shadow root)
// In these modes, vue-style-loader exports objects with the __inject__
Expand All @@ -89,10 +90,9 @@ module.exports = function genStyleInjectionCode (
styles.forEach((style, i) => {
if (isNotEmptyStyle(style)) {
const request = genStyleRequest(style, i)
styleInjectionCode += (
styleInjectionCode +=
`var style${i} = require(${request})\n` +
`if (style${i}.__inject__) style${i}.__inject__(context)\n`
)
if (style.module) genCSSModulesCode(style, request, i)
}
})
Expand All @@ -112,11 +112,15 @@ function injectStyles (context) {
${styleInjectionCode}
}
${needsHotReload ? `
${
needsHotReload
? `
module.hot && module.hot.dispose(function (data) {
disposed = true
})
` : ``}
`
: ``
}
${cssModulesHotReloadCode}
`.trim()
Expand Down
7 changes: 1 addition & 6 deletions lib/codegen/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,7 @@ const qs = require('querystring')

// these are built-in query parameters so should be ignored
// if the user happen to add them as attrs
const ignoreList = [
'id',
'index',
'src',
'type'
]
const ignoreList = ['id', 'index', 'src', 'type']

// transform the attrs on a SFC block descriptor into a resourceQuery string
exports.attrsToQuery = (attrs, langFallback) => {
Expand Down
4 changes: 2 additions & 2 deletions lib/compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@ exports.resolveCompiler = function (ctx, loaderContext) {
})
}

function loadFromContext (path, ctx) {
function loadFromContext(path, ctx) {
return require(require.resolve(path, {
paths: [ctx]
}))
}

function loadTemplateCompiler (ctx, loaderContext) {
function loadTemplateCompiler(ctx, loaderContext) {
try {
return loadFromContext('vue-template-compiler', ctx)
} catch (e) {
Expand Down
47 changes: 47 additions & 0 deletions lib/descriptorCache.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
const fs = require('fs')
const path = require('path')
const { resolveCompiler } = require('./compiler')

const cache = new Map()

exports.setDescriptor = function setDescriptor(filename, entry) {
cache.set(cleanQuery(filename), entry)
}

exports.getDescriptor = function getDescriptor(
filename,
options,
loaderContext
) {
filename = cleanQuery(filename)
if (cache.has(filename)) {
return cache.get(filename)
}

// This function should only be called after the descriptor has been
// cached by the main loader.
// If this is somehow called without a cache hit, it's probably due to sub
// loaders being run in separate threads. The only way to deal with this is to
// read from disk directly...
const source = fs.readFileSync(filename, 'utf-8')
const sourceRoot = path.dirname(
path.relative(loaderContext.rootContext, loaderContext.resourcePath)
)
const { compiler, templateCompiler } = resolveCompiler(
loaderContext.rootContext
)
const descriptor = compiler.parse({
source,
compiler: options.compiler || templateCompiler,
filename,
sourceRoot,
needMap: loaderContext.sourceMap
})
cache.set(filename, descriptor)
return descriptor
}

function cleanQuery(str) {
const i = str.indexOf('?')
return i > 0 ? str.slice(0, i) : str
}
13 changes: 7 additions & 6 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const genCustomBlocksCode = require('./codegen/customBlocks')
const componentNormalizerPath = require.resolve('./runtime/componentNormalizer')
const { NS } = require('./plugin')
const { resolveCompiler } = require('./compiler')
const { setDescriptor } = require('./descriptorCache')

let errorEmitted = false

Expand All @@ -27,7 +28,7 @@ module.exports = function (source) {
errorEmitted = true
}

const stringifyRequest = r => loaderUtils.stringifyRequest(loaderContext, r)
const stringifyRequest = (r) => loaderUtils.stringifyRequest(loaderContext, r)

const {
target,
Expand All @@ -52,10 +53,7 @@ module.exports = function (source) {
const context = rootContext || process.cwd()
const sourceRoot = path.dirname(path.relative(context, resourcePath))

const { compiler, templateCompiler } = resolveCompiler(
rootContext,
loaderContext
)
const { compiler, templateCompiler } = resolveCompiler(context, loaderContext)

const descriptor = compiler.parse({
source,
Expand All @@ -65,6 +63,9 @@ module.exports = function (source) {
needMap: sourceMap
})

// cache descriptor
setDescriptor(filename, descriptor)

// if the query has a type field, this is a language block request
// e.g. foo.vue?type=template&id=xxxxx
// and we will return early
Expand Down Expand Up @@ -92,7 +93,7 @@ module.exports = function (source) {
)

// feature information
const hasScoped = descriptor.styles.some(s => s.scoped)
const hasScoped = descriptor.styles.some((s) => s.scoped)
const hasFunctional =
descriptor.template && descriptor.template.attrs.functional
const needsHotReload =
Expand Down
54 changes: 27 additions & 27 deletions lib/loaders/pitcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,18 @@ const templateLoaderPath = require.resolve('./templateLoader')
const stylePostLoaderPath = require.resolve('./stylePostLoader')
const { resolveCompiler } = require('../compiler')

const isESLintLoader = l => /(\/|\\|@)eslint-loader/.test(l.path)
const isNullLoader = l => /(\/|\\|@)null-loader/.test(l.path)
const isCSSLoader = l => /(\/|\\|@)css-loader/.test(l.path)
const isCacheLoader = l => /(\/|\\|@)cache-loader/.test(l.path)
const isPitcher = l => l.path !== __filename
const isPreLoader = l => !l.pitchExecuted
const isPostLoader = l => l.pitchExecuted

const dedupeESLintLoader = loaders => {
const isESLintLoader = (l) => /(\/|\\|@)eslint-loader/.test(l.path)
const isNullLoader = (l) => /(\/|\\|@)null-loader/.test(l.path)
const isCSSLoader = (l) => /(\/|\\|@)css-loader/.test(l.path)
const isCacheLoader = (l) => /(\/|\\|@)cache-loader/.test(l.path)
const isPitcher = (l) => l.path !== __filename
const isPreLoader = (l) => !l.pitchExecuted
const isPostLoader = (l) => l.pitchExecuted

const dedupeESLintLoader = (loaders) => {
const res = []
let seen = false
loaders.forEach(l => {
loaders.forEach((l) => {
if (!isESLintLoader(l)) {
res.push(l)
} else if (!seen) {
Expand All @@ -28,8 +28,8 @@ const dedupeESLintLoader = loaders => {
return res
}

const shouldIgnoreCustomBlock = loaders => {
const actualLoaders = loaders.filter(loader => {
const shouldIgnoreCustomBlock = (loaders) => {
const actualLoaders = loaders.filter((loader) => {
// vue-loader
if (loader.path === selfPath) {
return false
Expand All @@ -45,7 +45,7 @@ const shouldIgnoreCustomBlock = loaders => {
return actualLoaders.length === 0
}

module.exports = code => code
module.exports = (code) => code

// This pitching loader is responsible for intercepting all vue block requests
// and transform it into appropriate requests.
Expand All @@ -62,7 +62,7 @@ module.exports.pitch = function (remainingRequest) {
// if this is an inline block, since the whole file itself is being linted,
// remove eslint-loader to avoid duplicate linting.
if (/\.vue$/.test(this.resourcePath)) {
loaders = loaders.filter(l => !isESLintLoader(l))
loaders = loaders.filter((l) => !isESLintLoader(l))
} else {
// This is a src import. Just make sure there's not more than 1 instance
// of eslint present.
Expand All @@ -78,7 +78,7 @@ module.exports.pitch = function (remainingRequest) {
return
}

const genRequest = loaders => {
const genRequest = (loaders) => {
// Important: dedupe since both the original rule
// and the cloned rule would match a source import request.
// also make sure to dedupe based on loader path.
Expand All @@ -90,7 +90,7 @@ module.exports.pitch = function (remainingRequest) {
const seen = new Map()
const loaderStrings = []

loaders.forEach(loader => {
loaders.forEach((loader) => {
const identifier =
typeof loader === 'string' ? loader : loader.path + loader.query
const request = typeof loader === 'string' ? loader : loader.request
Expand Down Expand Up @@ -133,17 +133,17 @@ module.exports.pitch = function (remainingRequest) {
const cacheLoader =
cacheDirectory && cacheIdentifier
? [
`${require.resolve('cache-loader')}?${JSON.stringify({
// For some reason, webpack fails to generate consistent hash if we
// use absolute paths here, even though the path is only used in a
// comment. For now we have to ensure cacheDirectory is a relative path.
cacheDirectory: (path.isAbsolute(cacheDirectory)
? path.relative(process.cwd(), cacheDirectory)
: cacheDirectory
).replace(/\\/g, '/'),
cacheIdentifier: hash(cacheIdentifier) + '-vue-loader-template'
})}`
]
`${require.resolve('cache-loader')}?${JSON.stringify({
// For some reason, webpack fails to generate consistent hash if we
// use absolute paths here, even though the path is only used in a
// comment. For now we have to ensure cacheDirectory is a relative path.
cacheDirectory: (path.isAbsolute(cacheDirectory)
? path.relative(process.cwd(), cacheDirectory)
: cacheDirectory
).replace(/\\/g, '/'),
cacheIdentifier: hash(cacheIdentifier) + '-vue-loader-template'
})}`
]
: []

const preLoaders = loaders.filter(isPreLoader)
Expand Down
Loading

0 comments on commit 55de28c

Please sign in to comment.