提交 d0d1bbb4 编写于 作者: fxy060608's avatar fxy060608

feat(cli): auto components

上级 93caa241
......@@ -308,7 +308,7 @@ function getGlobalUsingComponentsCode () {
return ''
}
return generateGlobalUsingComponentsCode(usingComponents)
}
}
function getUsingComponentsCode (pagePath) {
const usingComponents = usingComponentsPages[pagePath]
......@@ -325,6 +325,35 @@ function addPageUsingComponents (pagePath, usingComponents) {
usingComponentsPages[pagePath] = usingComponents
}
}
// 存储自动组件
const autoComponentMap = {}
function addAutoComponent (name) {
const options = process.UNI_AUTO_COMPONENTS
const opt = options.find(opt => opt.test(name))
if (!opt) { // 不匹配
return (autoComponentMap[name] = true) // cache
}
return (autoComponentMap[name] = {
name,
identifier: capitalize(camelize(name + '-auto-import')),
source: name.replace(opt.test, opt.replacement)
})
}
function getAutoComponents (autoComponents) {
const components = []
autoComponents.forEach(name => {
let autoComponent = autoComponentMap[name]
if (!autoComponent) {
autoComponent = addAutoComponent(name)
}
if (autoComponent !== true) {
components.push(autoComponent)
}
})
return components
}
module.exports = {
getMainEntry,
......@@ -334,7 +363,8 @@ module.exports = {
getPagesJson,
parsePagesJson,
pagesJsonJsFileName,
addPageUsingComponents,
getAutoComponents,
addPageUsingComponents,
getUsingComponentsCode,
generateUsingComponentsCode,
getGlobalUsingComponentsCode,
......
const compiler = require('../lib')
const res = compiler.compile(
`
<view class="custom-class"></view>
<view class="custom-class">
<uni-badge/>
<uni-badge/>
<uni-tag/>
<uni-tag/>
</view>
`, {
miniprogram: true,
resourcePath: '/User/fxy/Documents/test.wxml',
......@@ -14,9 +19,9 @@ const res = compiler.compile(
mp: {
platform: 'app-plus'
},
filterModules: ['swipe'],
filterModules: ['swipe']
// service: true,
view: true
// view: true
})
console.log(require('util').inspect(res, {
......
const {
isComponent
} = require('./util')
module.exports = {
preTransformNode (el, options) {
if (isComponent(el.tag)) {
// 挂在 isReservedTag 上边,可以保证外部访问到
(options.isReservedTag.autoComponents || (options.isReservedTag.autoComponents = new Set())).add(el.tag)
}
}
}
......@@ -26,8 +26,25 @@ const {
isComponent
} = require('./util')
function compileTemplate (source, options) {
const res = compile(source, options)
console.log(options)
const {
autoComponents
} = options.isReservedTag
if (autoComponents) {
console.log('检测到的自定义组件:' + [...autoComponents])
}
res.components = `{
'uni-badge': require('@components/uni-badge/uni-badge.vue').default,
'uni-tag': require('@components/uni-tag/uni-tag.vue').default
}`
return res
}
module.exports = {
compile (source, options = {}) {
(options.modules || (options.modules = [])).push(require('./auto-components'))
if (options.service) {
(options.modules || (options.modules = [])).push(require('./app/service'))
options.optimize = false // 启用 staticRenderFns
......@@ -37,7 +54,7 @@ module.exports = {
options.getTagNamespace = () => false
try {
return compile(source, options)
return compileTemplate(source, options)
} catch (e) {
console.error(source)
throw e
......@@ -47,7 +64,7 @@ module.exports = {
options.optimize = false // 暂不启用 staticRenderFns
options.isReservedTag = (tagName) => false // 均为组件
try {
return compile(source, options)
return compileTemplate(source, options)
} catch (e) {
console.error(source)
throw e
......@@ -55,7 +72,7 @@ module.exports = {
}
if (!options.mp) { // h5
return compile(source, options)
return compileTemplate(source, options)
}
(options.modules || (options.modules = [])).push(compilerModule)
......@@ -64,7 +81,7 @@ module.exports = {
options.modules.push(compilerAlipayModule)
}
const res = compile(source, Object.assign(options, {
const res = compileTemplate(source, Object.assign(options, {
optimize: false
}))
......
......@@ -6,9 +6,9 @@ const {
METHOD_RENDER_LIST
} = require('./constants')
function cached (fn) {
function cached(fn) {
const cache = Object.create(null)
return function cachedFn (str) {
return function cachedFn(str) {
const hit = cache[str]
return hit || (cache[str] = fn(str))
}
......@@ -21,7 +21,7 @@ const camelize = cached((str) => {
return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '')
})
function getCode (node) {
function getCode(node) {
return babelGenerate(t.cloneDeep(node), {
compact: true,
jsescOption: {
......@@ -31,11 +31,11 @@ function getCode (node) {
}).code
}
function traverseKey (ast, state) {
function traverseKey(ast, state) {
let forKey = false
babelTraverse(ast, {
noScope: true,
ObjectProperty (path) {
ObjectProperty(path) {
if (forKey) {
return
}
......@@ -44,7 +44,7 @@ function traverseKey (ast, state) {
path.stop()
}
},
CallExpression (path) {
CallExpression(path) {
if (path.node.callee.name === METHOD_RENDER_LIST) {
path.stop()
}
......@@ -53,7 +53,7 @@ function traverseKey (ast, state) {
return forKey
}
function traverseFilter (ast, state) {
function traverseFilter(ast, state) {
const filterModules = state.options.filterModules
if (!filterModules.length) {
return false
......@@ -61,7 +61,7 @@ function traverseFilter (ast, state) {
let isFilter = false
babelTraverse(ast, {
noScope: true,
Identifier (path) {
Identifier(path) {
if (filterModules.includes(path.node.name)) {
const parentNode = path.parent
if ( // t.msg || t['msg']
......@@ -81,11 +81,11 @@ function traverseFilter (ast, state) {
return isFilter
}
function wrapper (code, reverse = false) {
function wrapper(code, reverse = false) {
return reverse ? `{{!(${code})}}` : `{{${code}}}`
}
function genCode (node, noWrapper = false, reverse = false, quotes = true) {
function genCode(node, noWrapper = false, reverse = false, quotes = true) {
if (t.isStringLiteral(node)) {
return reverse ? `!(${node.value})` : node.value
} else if (t.isIdentifier(node)) {
......@@ -98,11 +98,11 @@ function genCode (node, noWrapper = false, reverse = false, quotes = true) {
return noWrapper ? code : wrapper(code, reverse)
}
function getForIndexIdentifier (id) {
function getForIndexIdentifier(id) {
return `__i${id}__`
}
function getForKey (forKey, forIndex, state) {
function getForKey(forKey, forIndex, state) {
if (forKey) {
if (t.isIdentifier(forKey)) {
if (forIndex !== forKey.name) { // 非 forIndex
......@@ -121,7 +121,7 @@ function getForKey (forKey, forIndex, state) {
return ''
}
function processMemberProperty (node, state) {
function processMemberProperty(node, state) {
if (node.computed) {
const property = node.property
if (t.isNumericLiteral(property)) {
......@@ -139,7 +139,7 @@ function processMemberProperty (node, state) {
}
}
function processMemberExpression (element, state) {
function processMemberExpression(element, state) {
// item['order']=>item.order
if (t.isMemberExpression(element)) {
element = t.cloneDeep(element)
......@@ -152,19 +152,19 @@ function processMemberExpression (element, state) {
babelTraverse(element, {
noScope: true,
MemberExpression (path) {
MemberExpression(path) {
processMemberProperty(path.node, state)
}
})
babelTraverse(element, {
noScope: true,
MemberExpression (path) {
MemberExpression(path) {
if (t.isStringLiteral(path.node.property)) {
path.node.computed = false
}
},
StringLiteral (path) {
StringLiteral(path) {
path.replaceWith(t.identifier(path.node.value))
}
})
......@@ -172,7 +172,7 @@ function processMemberExpression (element, state) {
return element
}
function hasOwn (obj, key) {
function hasOwn(obj, key) {
return hasOwnProperty.call(obj, key)
}
......@@ -182,9 +182,10 @@ const {
getTagName
} = require('./h5')
function isComponent (tagName) {
function isComponent(tagName) {
if (
tagName === 'block' ||
tagName === 'component' ||
tagName === 'template' ||
tagName === 'keep-alive'
) {
......@@ -193,7 +194,21 @@ function isComponent (tagName) {
return !hasOwn(tags, getTagName(tagName.replace('v-uni-', '')))
}
function makeMap(str, expectsLowerCase) {
const map = Object.create(null)
const list = str.split(',')
for (let i = 0; i < list.length; i++) {
map[list[i]] = true
}
return expectsLowerCase ?
val => map[val.toLowerCase()] :
val => map[val]
}
module.exports = {
isUnaryTag: makeMap(
'image,area,base,br,col,embed,frame,hr,img,input,isindex,keygen,' +
'link,meta,param,source,track,wbr'
),
isComponent,
genCode,
getCode,
......
const TAGS = [
const TAGS = [
'ad',
'text',
'image',
......@@ -94,6 +94,7 @@ if (process.env.UNI_USING_NVUE_COMPILER) {
module.exports = {
preserveWhitespace: false,
isAppNVue: true,
compiler: require('weex-template-compiler'),
compilerOptions: {
modules
......
const fs = require('fs')
const path = require('path')
const webpack = require('webpack')
const VueLoaderPlugin = require('vue-loader/lib/plugin')
const VueLoaderPlugin = require('@dcloudio/vue-cli-plugin-uni/packages/vue-loader/lib/plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const {
......@@ -36,21 +36,23 @@ const uniPath = process.env.UNI_USING_V8
? '../packages/uni-app-plus-nvue-v8/dist/index.js'
: '../packages/uni-app-plus-nvue/dist/index.js'
const provide = {}
const provide = {}
if (process.env.UNI_USING_V3 || process.env.UNI_USING_NATIVE) {
provide['uni.getCurrentSubNVue'] = [path.resolve(__dirname, '../packages/uni-app-plus-nvue/dist/get-current-sub-nvue.js'), 'default']
provide['uni.requireNativePlugin'] = [path.resolve(__dirname, '../packages/uni-app-plus-nvue/dist/require-native-plugin.js'), 'default']
}
if (!process.env.UNI_USING_V3) {
if (!process.env.UNI_USING_NATIVE) {
provide['uni'] = [path.resolve(__dirname, uniPath), 'default']
}
if (process.env.UNI_USING_V8) {
provide['plus'] = [path.resolve(__dirname, uniPath), 'weexPlus']
}
if (process.env.UNI_USING_V3 || process.env.UNI_USING_NATIVE) {
provide['uni.getCurrentSubNVue'] = [path.resolve(__dirname,
'../packages/uni-app-plus-nvue/dist/get-current-sub-nvue.js'), 'default']
provide['uni.requireNativePlugin'] = [path.resolve(__dirname,
'../packages/uni-app-plus-nvue/dist/require-native-plugin.js'), 'default']
}
if (!process.env.UNI_USING_V3) {
if (!process.env.UNI_USING_NATIVE) {
provide['uni'] = [path.resolve(__dirname, uniPath), 'default']
}
if (process.env.UNI_USING_V8) {
provide['plus'] = [path.resolve(__dirname, uniPath), 'weexPlus']
}
}
if (
......@@ -110,17 +112,9 @@ const rules = [{
}
},
{
test: /\.nvue(\?[^?]+)?$/,
use: [{
loader: path.resolve(__dirname, '../packages/vue-loader'),
options: vueLoaderOptions
}],
exclude: excludeModuleReg
},
{
test: /\.vue(\?[^?]+)?$/,
test: [/\.nvue(\?[^?]+)?$/, /\.vue(\?[^?]+)?$/],
use: [{
loader: path.resolve(__dirname, '../packages/vue-loader'),
loader: require.resolve('@dcloudio/vue-cli-plugin-uni/packages/vue-loader'),
options: vueLoaderOptions
}],
exclude: excludeModuleReg
......@@ -322,4 +316,4 @@ module.exports = function () {
zlib: false
}
}
}
}
......@@ -9,16 +9,8 @@ const {
// nvue override
moduleAlias.addAlias('weex-styler', path.resolve(__dirname, 'packages/weex-styler'))
moduleAlias.addAlias('weex-template-compiler', path.resolve(__dirname, 'packages/weex-template-compiler'))
moduleAlias.addAlias('./compileTemplate', path.resolve(__dirname,
'packages/webpack-uni-nvue-loader/lib/compileTemplate'))
moduleAlias.addAlias('./codegen/styleInjection', path.resolve(__dirname,
'packages/webpack-uni-nvue-loader/lib/styleInjection'))
moduleAlias.addAlias('./templateLoader', (fromPath, request, alias) => {
if (fromPath.indexOf('vue-loader') !== -1) {
return path.resolve(__dirname, 'packages/webpack-uni-nvue-loader/lib/templateLoader')
}
return request
})
moduleAlias.addAlias('./compileTemplate', require.resolve(
'@dcloudio/vue-cli-plugin-uni/packages/@vue/component-compiler-utils/dist/compileTemplate'))
if (isInHBuilderX) {
moduleAlias.addAlias('typescript', path.resolve(process.env.UNI_HBUILDERX_PLUGINS,
......
const path = require('path')
const hash = require('hash-sum')
const qs = require('querystring')
const plugin = require('vue-loader/lib/plugin')
const selectBlock = require('vue-loader/lib/select')
const loaderUtils = require('loader-utils')
const { attrsToQuery } = require('vue-loader/lib/codegen/utils')
const { parse } = require('@vue/component-compiler-utils')
const genStylesCode = require('../webpack-uni-nvue-loader/lib/styleInjection')
const { genHotReloadCode } = require('vue-loader/lib/codegen/hotReload')
const genCustomBlocksCode = require('vue-loader/lib/codegen/customBlocks')
const componentNormalizerPath = require.resolve('vue-loader/lib/runtime/componentNormalizer')
const { NS } = require('vue-loader/lib/plugin')
let errorEmitted = false
function loadTemplateCompiler (loaderContext) {
try {
return require('vue-template-compiler')
} catch (e) {
if (/version mismatch/.test(e.toString())) {
loaderContext.emitError(e)
} else {
loaderContext.emitError(new Error(
`[vue-loader] vue-template-compiler must be installed as a peer dependency, ` +
`or a compatible compiler implementation must be passed via options.`
))
}
}
}
function hasRecyclable (template) {
return !!(template && template.attrs && template.attrs.recyclable)
}
module.exports = function (source) {
const loaderContext = this
if (!errorEmitted && !loaderContext['thread-loader'] && !loaderContext[NS]) {
loaderContext.emitError(new Error(
`vue-loader was used without the corresponding plugin. ` +
`Make sure to include VueLoaderPlugin in your webpack config.`
))
errorEmitted = true
}
const stringifyRequest = r => loaderUtils.stringifyRequest(loaderContext, r)
const {
target,
request,
minimize,
sourceMap,
rootContext,
resourcePath,
resourceQuery
} = loaderContext
const rawQuery = resourceQuery.slice(1)
const inheritQuery = `&${rawQuery}`
const incomingQuery = qs.parse(rawQuery)
const options = loaderUtils.getOptions(loaderContext) || {}
const isServer = target === 'node'
const isShadow = !!options.shadowMode
const isProduction = options.productionMode || minimize || process.env.NODE_ENV === 'production'
const filename = path.basename(resourcePath)
const context = rootContext || process.cwd()
const sourceRoot = path.dirname(path.relative(context, resourcePath))
const descriptor = parse({
source,
compiler: options.compiler || loadTemplateCompiler(loaderContext),
filename,
sourceRoot,
needMap: sourceMap
})
// 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
if (incomingQuery.type) {
return selectBlock(
descriptor,
loaderContext,
incomingQuery,
!!options.appendExtension
)
}
// module id for scoped CSS & hot-reload
const rawShortFilePath = path
.relative(context, resourcePath)
.replace(/^(\.\.[\/\\])+/, '')
const shortFilePath = rawShortFilePath.replace(/\\/g, '/') + resourceQuery
const id = hash(
isProduction
? (shortFilePath + '\n' + source)
: shortFilePath
)
// feature information
const hasScoped = descriptor.styles.some(s => s.scoped)
const hasFunctional = descriptor.template && descriptor.template.attrs.functional
const needsHotReload = (
!isServer &&
!isProduction &&
(descriptor.script || descriptor.template) &&
options.hotReload !== false
)
// template
let templateImport = `var render, staticRenderFns`
let templateRequest
const recyclable = hasRecyclable(descriptor.template) // fixed by xxxxxx
if (descriptor.template) {
const src = descriptor.template.src || resourcePath
const idQuery = `&id=${id}`
const scopedQuery = hasScoped ? `&scoped=true` : ``
const attrsQuery = attrsToQuery(descriptor.template.attrs)
const query = `?vue&type=template${idQuery}${scopedQuery}${attrsQuery}${inheritQuery}`
const request = templateRequest = stringifyRequest(src + query)
if(recyclable){ // fixed by xxxxxx
templateImport = `import { render, staticRenderFns, recyclableRender } from ${request}`
} else {
templateImport = `import { render, staticRenderFns } from ${request}`
}
}
// script
let scriptImport = `var script = {}`
if (descriptor.script) {
const src = descriptor.script.src || resourcePath
const attrsQuery = attrsToQuery(descriptor.script.attrs, 'js')
const query = `?vue&type=script${attrsQuery}${inheritQuery}`
const request = stringifyRequest(src + query)
scriptImport = (
`import script from ${request}\n` +
`export * from ${request}` // support named exports
)
}
// styles
let stylesCode = ``
if (descriptor.styles.length) {
stylesCode = genStylesCode(
loaderContext,
descriptor.styles,
id,
resourcePath,
stringifyRequest,
needsHotReload,
isServer || isShadow // needs explicit injection?
)
}
let code = `
${templateImport}
${scriptImport}
${stylesCode}
/* normalize component */
import normalizer from ${stringifyRequest(`!${componentNormalizerPath}`)}
var component = normalizer(
script,
render,
staticRenderFns,
${hasFunctional ? `true` : `false`},
${`null`},
${hasScoped ? JSON.stringify(id) : `null`},
${isServer ? JSON.stringify(hash(request)) : `null`}
${isShadow ? `,true` : ``}
)
`.trim() + `\n`
if (descriptor.customBlocks && descriptor.customBlocks.length) {
code += genCustomBlocksCode(
descriptor.customBlocks,
resourcePath,
resourceQuery,
stringifyRequest
)
}
if (needsHotReload) {
code += `\n` + genHotReloadCode(id, hasFunctional, templateRequest)
}
if(/injectStyles/.test(stylesCode)){// fixed by xxxxxx
code +=`\ninjectStyles.call(component)`
}
// Expose filename. This is used by the devtools and Vue runtime warnings.
if (!isProduction) {
// Expose the file's full path in development, so that it can be opened
// from the devtools.
code += `\ncomponent.options.__file = ${JSON.stringify(rawShortFilePath.replace(/\\/g, '/'))}`
} else if (options.exposeFilename) {
// Libraies can opt-in to expose their components' filenames in production builds.
// For security reasons, only expose the file's basename in production.
code += `\ncomponent.options.__file = ${JSON.stringify(filename)}`
}
if(recyclable){
code += `\nrecyclableRender && (component.options["@render"] = recyclableRender)` // fixed by xxxxxx
}
code += `\nexport default component.exports`
// console.log(code)
return code
}
module.exports.VueLoaderPlugin = plugin
const qs = require('querystring')
const loaderUtils = require('loader-utils')
const hash = require('hash-sum')
const selfPath = require.resolve('vue-loader/lib/index')
const templateLoaderPath = require.resolve('vue-loader/lib/loaders/templateLoader')
const stylePostLoaderPath = require.resolve('vue-loader/lib/loaders/stylePostLoader')
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 => {
if (!isESLintLoader(l)) {
res.push(l)
} else if (!seen) {
seen = true
res.push(l)
}
})
return res
}
const shouldIgnoreCustomBlock = loaders => {
const actualLoaders = loaders.filter(loader => {
// vue-loader
if (loader.path === selfPath) {
return false
}
// cache-loader
if (isCacheLoader(loader)) {
return false
}
return true
})
return actualLoaders.length === 0
}
module.exports = code => code
// This pitching loader is responsible for intercepting all vue block requests
// and transform it into appropriate requests.
module.exports.pitch = function (remainingRequest) {
const options = loaderUtils.getOptions(this)
const { cacheDirectory, cacheIdentifier } = options
const query = qs.parse(this.resourceQuery.slice(1))
let loaders = this.loaders
// if this is a language block request, eslint-loader may get matched
// multiple times
if (query.type) {
// 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))
} else {
// This is a src import. Just make sure there's not more than 1 instance
// of eslint present.
loaders = dedupeESLintLoader(loaders)
}
}
// remove self
loaders = loaders.filter(isPitcher)
// do not inject if user uses null-loader to void the type (#1239)
if (loaders.some(isNullLoader)) {
return
}
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.
// assumes you'd probably never want to apply the same loader on the same
// file twice.
// Exception: in Vue CLI we do need two instances of postcss-loader
// for user config and inline minification. So we need to dedupe baesd on
// path AND query to be safe.
const seen = new Map()
const loaderStrings = []
loaders.forEach(loader => {
const identifier = typeof loader === 'string'
? loader
: (loader.path + loader.query)
const request = typeof loader === 'string' ? loader : loader.request
if (!seen.has(identifier)) {
seen.set(identifier, true)
// loader.request contains both the resolved loader path and its options
// query (e.g. ??ref-0)
loaderStrings.push(request)
}
})
return loaderUtils.stringifyRequest(this, '-!' + [
...loaderStrings,
this.resourcePath + this.resourceQuery
].join('!'))
}
// Inject style-post-loader before css-loader for scoped CSS and trimming
if (query.type === `style`) {
const cssLoaderIndex = loaders.findIndex(isCSSLoader)
if (cssLoaderIndex > -1) {
const afterLoaders = loaders.slice(0, cssLoaderIndex + 1)
const beforeLoaders = loaders.slice(cssLoaderIndex + 1)
const request = genRequest([
...afterLoaders,
stylePostLoaderPath,
...beforeLoaders
])
// console.log(request)
return `import mod from ${request}; export default mod; export * from ${request}`
}
}
// for templates: inject the template compiler & optional cache
if (query.type === `template`) {
const path = require('path')
// fixed by xxxxxx
const cacheLoader = cacheDirectory && cacheIdentifier
? [`${require.resolve('cache-loader')}??uni-cache-loader-template-options`]
: []
const preLoaders = loaders.filter(isPreLoader)
const postLoaders = loaders.filter(isPostLoader)
const request = genRequest([
...cacheLoader,
...postLoaders,
templateLoaderPath + `??vue-loader-options`,
...preLoaders
])
// console.log(request)
// the template compiler uses esm exports
return `export * from ${request}`
}
// if a custom block has no other matching loader other than vue-loader itself
// or cache-loader, we should ignore it
if (query.type === `custom` && shouldIgnoreCustomBlock(loaders)) {
return ``
}
// When the user defines a rule that has only resourceQuery but no test,
// both that rule and the cloned rule will match, resulting in duplicated
// loaders. Therefore it is necessary to perform a dedupe here.
const request = genRequest(loaders)
return `import mod from ${request}; export default mod; export * from ${request}`
}
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const assetUrl_1 = __importDefault(require("@vue/component-compiler-utils/dist/templateCompilerModules/assetUrl"));
const srcset_1 = __importDefault(require("@vue/component-compiler-utils/dist/templateCompilerModules/srcset"));
const consolidate = require('consolidate');
const transpile = require('vue-template-es2015-compiler');
function compileTemplate(options) {
const { preprocessLang } = options;
const preprocessor = preprocessLang && consolidate[preprocessLang];
if (preprocessor) {
return actuallyCompile(Object.assign({}, options, {
source: preprocess(options, preprocessor)
}));
}
else if (preprocessLang) {
return {
code: `var render = function () {}\n` + `var staticRenderFns = []\n`,
source: options.source,
tips: [
`Component ${options.filename} uses lang ${preprocessLang} for template. Please install the language preprocessor.`
],
errors: [
`Component ${options.filename} uses lang ${preprocessLang} for template, however it is not installed.`
]
};
}
else {
return actuallyCompile(options);
}
}
exports.compileTemplate = compileTemplate;
function preprocess(options, preprocessor) {
const { source, filename, preprocessOptions } = options;
const finalPreprocessOptions = Object.assign({
filename
}, preprocessOptions);
// Consolidate exposes a callback based API, but the callback is in fact
// called synchronously for most templating engines. In our case, we have to
// expose a synchronous API so that it is usable in Jest transforms (which
// have to be sync because they are applied via Node.js require hooks)
let res, err;
preprocessor.render(source, finalPreprocessOptions, (_err, _res) => {
if (_err)
err = _err;
res = _res;
});
if (err)
throw err;
return res;
}
function actuallyCompile(options) {
const { source, compiler, compilerOptions = {}, transpileOptions = {}, transformAssetUrls, isProduction = process.env.NODE_ENV === 'production', isFunctional = false, optimizeSSR = false, prettify = true } = options;
const compile = optimizeSSR && compiler.ssrCompile ? compiler.ssrCompile : compiler.compile;
let finalCompilerOptions = compilerOptions;
if (transformAssetUrls) {
const builtInModules = [
transformAssetUrls === true
? assetUrl_1.default()
: assetUrl_1.default(transformAssetUrls),
srcset_1.default()
];
finalCompilerOptions = Object.assign({}, compilerOptions, {
modules: [...builtInModules, ...(compilerOptions.modules || [])]
});
}
const { render, staticRenderFns, tips, errors, '@render': recyclableRender } = compile(source, finalCompilerOptions);
if (errors && errors.length) {
return {
code: `var render = function () {}\n` + `var staticRenderFns = []\n`,
source,
tips,
errors
};
}
else {
const finalTranspileOptions = Object.assign({}, transpileOptions, {
transforms: Object.assign({}, transpileOptions.transforms, {
stripWithFunctional: isFunctional
})
});
const toFunction = (code) => {
return `function (${isFunctional ? `_h,_vm` : ``}) {${code}}`;
};
// transpile code with vue-template-es2015-compiler, which is a forked
// version of Buble that applies ES2015 transforms + stripping `with` usage
let code = transpile(`var __render__ = ${toFunction(render)}\n` +
(recyclableRender ? (`var __recyclableRender__ = ${toFunction(recyclableRender)}\n`) : '') +
`var __staticRenderFns__ = [${staticRenderFns.map(toFunction)}]`, finalTranspileOptions) + `\n`;
// #23 we use __render__ to avoid `render` not being prefixed by the
// transpiler when stripping with, but revert it back to `render` to
// maintain backwards compat
code = code.replace(/\s__(render|recyclableRender|staticRenderFns)__\s/g, ' $1 ');
if (!isProduction) {
// mark with stripped (this enables Vue to use correct runtime proxy
// detection)
code += `render._withStripped = true`;
if (prettify) {
code = require('prettier').format(code, {
semi: false,
parser: 'babel'
});
}
}
return {
code,
source,
tips,
errors
};
}
}
const { attrsToQuery } = require('vue-loader/lib/codegen/utils')
const hotReloadAPIPath = JSON.stringify(require.resolve('vue-hot-reload-api'))
const nonWhitespaceRE = /\S+/
module.exports = function genStyleInjectionCode (
loaderContext,
styles,
id,
resourcePath,
stringifyRequest,
needsHotReload,
needsExplicitInjection
) {
let styleImportsCode = ``
let styleInjectionCode = ``
let cssModulesHotReloadCode = ``
let hasCSSModules = false
const cssModuleNames = new Map()
function genStyleRequest (style, i) {
const src = style.src || resourcePath
const attrsQuery = attrsToQuery(style.attrs, 'css')
const inheritQuery = `&${loaderContext.resourceQuery.slice(1)}`
// 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.scoped ? `&id=${id}` : ``
const query = `?vue&type=style&index=${i}${idQuery}${attrsQuery}${inheritQuery}`
return stringifyRequest(src + query)
}
function genCSSModulesCode (style, request, i) {
hasCSSModules = true
const moduleName = style.module === true ? '$style' : style.module
if (cssModuleNames.has(moduleName)) {
loaderContext.emitError(`CSS module name ${moduleName} is not unique!`)
}
cssModuleNames.set(moduleName, true)
// `(vue-)style-loader` exports the name-to-hash map directly
// `css-loader` exports it in `.locals`
const locals = `(style${i}.locals || style${i})`
const name = JSON.stringify(moduleName)
if (!needsHotReload) {
styleInjectionCode += `this[${name}] = ${locals}\n`
} else {
styleInjectionCode += `
cssModules[${name}] = ${locals}
Object.defineProperty(this, ${name}, {
configurable: true,
get: function () {
return cssModules[${name}]
}
})
`
cssModulesHotReloadCode += `
module.hot && module.hot.accept([${request}], function () {
var oldLocals = cssModules[${name}]
if (oldLocals) {
var newLocals = require(${request})
if (JSON.stringify(newLocals) !== JSON.stringify(oldLocals)) {
cssModules[${name}] = newLocals
require(${hotReloadAPIPath}).rerender("${id}")
}
}
})
`
}
}
// empty styles: with no `src` specified or only contains whitespaces
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__
// method; otherwise we simply import the styles.
if (!needsExplicitInjection) {
styles.forEach((style, i) => {
// do not generate requests for empty styles
if (isNotEmptyStyle(style)) {
const request = genStyleRequest(style, i)
styleImportsCode += `import style${i} from ${request}\n`
if (style.module) genCSSModulesCode(style, request, i)
}
})
} else {
styleInjectionCode = `if(!this.options.style){
this.options.style = {}
}
if(Vue.prototype.__merge_style && Vue.prototype.__$appStyle__){
Vue.prototype.__merge_style(Vue.prototype.__$appStyle__, this.options.style)
}
`
styles.forEach((style, i) => {
if (isNotEmptyStyle(style)) {
const request = genStyleRequest(style, i)
styleInjectionCode += (
`if(Vue.prototype.__merge_style){
Vue.prototype.__merge_style(require(${request}).default, this.options.style)
}else{
Object.assign(this.options.style,require(${request}).default)
}\n`//fixed by xxxxxx 简单处理,与 weex-vue-loader 保持一致
//`var style${i} = require(${request})\n` +
//`if (style${i}.__inject__) style${i}.__inject__(context)\n`
)
if (style.module) genCSSModulesCode(style, request, i)
}
})
}
if (!needsExplicitInjection && !hasCSSModules) {
return styleImportsCode
}
return `
${styleImportsCode}
${hasCSSModules && needsHotReload ? `var cssModules = {}` : ``}
${needsHotReload ? `var disposed = false` : ``}
function injectStyles () {
${styleInjectionCode}
}
${needsHotReload ? `
module.hot && module.hot.dispose(function (data) {
disposed = true
})
` : ``}
${cssModulesHotReloadCode}
`.trim()
}
const qs = require('querystring')
const loaderUtils = require('loader-utils')
const { compileTemplate } = require('@vue/component-compiler-utils')
// Loader that compiles raw template into JavaScript functions.
// This is injected by the global pitcher (../pitch) for template
// selection requests initiated from vue files.
module.exports = function (source) {
const loaderContext = this
const query = qs.parse(this.resourceQuery.slice(1))
// although this is not the main vue-loader, we can get access to the same
// vue-loader options because we've set an ident in the plugin and used that
// ident to create the request for this loader in the pitcher.
const options = loaderUtils.getOptions(loaderContext) || {}
const { id } = query
const isServer = loaderContext.target === 'node'
const isProduction = options.productionMode || loaderContext.minimize || process.env.NODE_ENV === 'production'
const isFunctional = query.functional
// allow using custom compiler via options
const compiler = options.compiler || require('vue-template-compiler')
const compilerOptions = Object.assign({
outputSourceRange: true
}, options.compilerOptions, {
scopeId: query.scoped ? `data-v-${id}` : null,
comments: query.comments,
filename: this.resourcePath // fixed by xxxxxx 传递 filename
})
// for vue-component-compiler
const finalOptions = {
source,
filename: this.resourcePath,
compiler,
compilerOptions,
// allow customizing behavior of vue-template-es2015-compiler
transpileOptions: options.transpileOptions,
transformAssetUrls: options.transformAssetUrls || true,
isProduction,
isFunctional,
optimizeSSR: isServer && options.optimizeSSR !== false,
prettify: options.prettify
}
const compiled = compileTemplate(finalOptions)
// tips
if (compiled.tips && compiled.tips.length) {
compiled.tips.forEach(tip => {
loaderContext.emitWarning(typeof tip === 'object' ? tip.msg : tip)
})
}
// errors
if (compiled.errors && compiled.errors.length) {
// 2.6 compiler outputs errors as objects with range
if (compiler.generateCodeFrame && finalOptions.compilerOptions.outputSourceRange) {
// TODO account for line offset in case template isn't placed at top
// of the file
loaderContext.emitError(
`\n\n Errors compiling template:\n\n` +
compiled.errors.map(({ msg, start, end }) => {
const frame = compiler.generateCodeFrame(source, start, end)
return ` ${msg}\n\n${pad(frame)}`
}).join(`\n\n`) +
'\n at ' + finalOptions.filename + ':0' // fixed by xxxxxx
)
} else {
loaderContext.emitError(
`\n Error compiling template:\n${pad(compiled.source)}\n` +
compiled.errors.map(e => ` - ${e}`).join('\n') +
'\n at ' + finalOptions.filename + ':0' // fixed by xxxxxx
)
}
}
const { code } = compiled
if(query.recyclable) {// fixed by xxxxxx
return code + `\nexport { render, recyclableRender, staticRenderFns }`
}
// finish with ESM exports
return code + `\nexport { render, staticRenderFns }`
}
function pad (source) {
return source
.split(/\r?\n/)
.map(line => ` ${line}`)
.join('\n')
}
......@@ -320,6 +320,13 @@ if (
}
}
process.UNI_AUTO_COMPONENTS = [{
test (str) {
return new RegExp('uni-(.*)').test(str)
},
replacement: '@components/uni-$1/uni-$1.vue'
}]
runByHBuilderX && console.log(`正在编译中...`)
module.exports = {
......
......@@ -4,7 +4,7 @@ const {
const {
isUnaryTag
} = require('../util')
} = require('@dcloudio/uni-template-compiler/lib/util')
const simplePathRE = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['[^']*?']|\["[^"]*?"]|\[\d+]|\[[A-Za-z_$][\w$]*])*$/
......@@ -69,9 +69,7 @@ module.exports = {
isUnaryTag,
preserveWhitespace: false,
modules: [require('../format-text'), {
preTransformNode (el, {
warn
}) {
preTransformNode (el, options) {
fixBooleanAttribute(el)
if (el.tag.indexOf('v-uni-') === 0) {
addTag(el.tag.replace('v-uni-', ''))
......
......@@ -3,10 +3,15 @@ const {
convertStaticStyle
} = require('@dcloudio/uni-cli-shared')
const {
isUnaryTag
} = require('@dcloudio/uni-template-compiler/lib/util')
module.exports = {
isUnaryTag,
preserveWhitespace: false,
modules: [require('./format-text'), {
preTransformNode (el, {
preTransformNode(el, {
warn
}) {
if (el.attrsMap) {
......@@ -35,7 +40,7 @@ module.exports = {
})
}
},
postTransformNode (el) {
postTransformNode(el) {
if (process.env.UNI_PLATFORM === 'mp-alipay') {
if (el.tag === 'slot') {
if (!el.children.length) {
......
function makeMap (str, expectsLowerCase) {
const map = Object.create(null)
const list = str.split(',')
for (let i = 0; i < list.length; i++) {
map[list[i]] = true
}
return expectsLowerCase
? val => map[val.toLowerCase()]
: val => map[val]
}
let partialIdentifier = false
module.exports = {
isUnaryTag: makeMap(
'image,area,base,br,col,embed,frame,hr,img,input,isindex,keygen,' +
'link,meta,param,source,track,wbr'
),
getPartialIdentifier () {
if (!partialIdentifier) {
partialIdentifier = {
......
......@@ -9,7 +9,7 @@ module.exports = function genStyleInjectionCode (
resourcePath,
stringifyRequest,
needsHotReload,
needsExplicitInjection
needsExplicitInjection // fixed by xxxxxx nvue 时,指定了 target 为 node,激活 needsExplicitInjection
) {
let styleImportsCode = ``
let styleInjectionCode = ``
......@@ -85,13 +85,25 @@ module.exports = function genStyleInjectionCode (
if (style.module) genCSSModulesCode(style, request, i)
}
})
} else {
} else { // fixed by xxxxxx nvue style
styleInjectionCode = `if(!this.options.style){
this.options.style = {}
}
if(Vue.prototype.__merge_style && Vue.prototype.__$appStyle__){
Vue.prototype.__merge_style(Vue.prototype.__$appStyle__, this.options.style)
}
`
styles.forEach((style, i) => {
if (isNotEmptyStyle(style)) {
const request = genStyleRequest(style, i)
styleInjectionCode += (
`var style${i} = require(${request})\n` +
`if (style${i}.__inject__) style${i}.__inject__(context)\n`
`if(Vue.prototype.__merge_style){
Vue.prototype.__merge_style(require(${request}).default, this.options.style)
}else{
Object.assign(this.options.style,require(${request}).default)
}\n`//fixed by xxxxxx 简单处理,与 weex-vue-loader 保持一致
//`var style${i} = require(${request})\n` +
//`if (style${i}.__inject__) style${i}.__inject__(context)\n`
)
if (style.module) genCSSModulesCode(style, request, i)
}
......
import { Plugin } from 'webpack'
import { VueTemplateCompiler } from '@vue/component-compiler-utils/lib/types'
import { VueTemplateCompiler } from '@vue/component-compiler-utils/dist/types'
import { CompilerOptions } from 'vue-template-compiler'
declare namespace VueLoader {
......
......@@ -108,21 +108,26 @@ module.exports = function (source) {
)
// template
let templateImport = `var render, staticRenderFns`
// fixed by xxxxxx (recyclable,auto components)
let recyclable
let templateImport = `var render, staticRenderFns, recyclableRender, components`
let templateRequest
if (descriptor.template) {
// fixed by xxxxxx
recyclable = options.isAppNVue && !!(template.attrs && template.attrs.recyclable)
const src = descriptor.template.src || resourcePath
const idQuery = `&id=${id}`
const scopedQuery = hasScoped ? `&scoped=true` : ``
const attrsQuery = attrsToQuery(descriptor.template.attrs)
const query = `?vue&type=template${idQuery}${scopedQuery}${attrsQuery}${inheritQuery}`
const request = templateRequest = stringifyRequest(src + query)
templateImport = `import { render, staticRenderFns } from ${request}`
// fixed by xxxxxx (auto components)
templateImport = `import { render, staticRenderFns, recyclableRender, components } from ${request}`
}
// script
let scriptImport = `var script = {}`
if (descriptor.script) {// fixed by xxxxxx view 层的 script 在 script-loader 中提取自定义组件信息
if (descriptor.script) {
const src = descriptor.script.src || resourcePath
const attrsQuery = attrsToQuery(descriptor.script.attrs, 'js')
const query = `?vue&type=script${attrsQuery}${inheritQuery}`
......@@ -135,7 +140,8 @@ module.exports = function (source) {
// styles
let stylesCode = ``
if (options.isAppView && descriptor.styles.length) {// fixed by xxxxxx 仅限 view 层
// fixed by xxxxxx 仅限 view 层
if (!options.isAppService && descriptor.styles.length) {
stylesCode = genStylesCode(
loaderContext,
descriptor.styles,
......@@ -146,7 +152,7 @@ module.exports = function (source) {
isServer || isShadow // needs explicit injection?
)
}
// fixed by xxxxxx (injectStyles,auto components)
let code = `
${templateImport}
${scriptImport}
......@@ -159,10 +165,11 @@ var component = normalizer(
render,
staticRenderFns,
${hasFunctional ? `true` : `false`},
${/injectStyles/.test(stylesCode) ? `injectStyles` : `null`},
${options.isAppNVue ? `null`: (/injectStyles/.test(stylesCode) ? `injectStyles` : `null`)},
${hasScoped ? JSON.stringify(id) : `null`},
${isServer ? JSON.stringify(hash(request)) : `null`}
${isShadow ? `,true` : ``}
${isShadow ? `,true` : ``},
components
)
`.trim() + `\n`
......@@ -178,7 +185,10 @@ var component = normalizer(
if (needsHotReload) {
code += `\n` + genHotReloadCode(id, hasFunctional, templateRequest)
}
// fixed by xxxxxx (app-nvue injectStyles)
if (options.isAppNVue && /injectStyles/.test(stylesCode)) {
code +=`\ninjectStyles.call(component)`
}
// Expose filename. This is used by the devtools and Vue runtime warnings.
if (!isProduction) {
// Expose the file's full path in development, so that it can be opened
......@@ -189,7 +199,9 @@ var component = normalizer(
// For security reasons, only expose the file's basename in production.
code += `\ncomponent.options.__file = ${JSON.stringify(filename)}`
}
if (recyclable) { // fixed by xxxxxx app-plus recyclable
code += `\nrecyclableRender && (component.options["@render"] = recyclableRender)` // fixed by xxxxxx
}
code += `\nexport default component.exports`
// console.log(code)
return code
......
......@@ -127,16 +127,20 @@ module.exports.pitch = function (remainingRequest) {
// for templates: inject the template compiler & optional cache
if (query.type === `template`) {
const path = require('path')
// 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'
// })}`]
// : []
// fixed by xxxxxx 定制 template cache
const cacheLoader = cacheDirectory && cacheIdentifier
? [`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')}??uni-cache-loader-template-options`]
: []
const preLoaders = loaders.filter(isPreLoader)
......
......@@ -64,21 +64,21 @@ module.exports = function (source) {
const frame = compiler.generateCodeFrame(source, start, end)
return ` ${msg}\n\n${pad(frame)}`
}).join(`\n\n`) +
'\n'
'\n at ' + finalOptions.filename + ':0' // fixed by xxxxxx
)
} else {
loaderContext.emitError(
`\n Error compiling template:\n${pad(compiled.source)}\n` +
compiled.errors.map(e => ` - ${e}`).join('\n') +
'\n'
'\n at ' + finalOptions.filename + ':0' // fixed by xxxxxx
)
}
}
const { code } = compiled
// finish with ESM exports
return code + `\nexport { render, staticRenderFns }`
// fixed by xxxxxx recyclableRender, components
// finish with ESM exports
return code + `\nexport { render, staticRenderFns, recyclableRender, components }`
}
function pad (source) {
......
const qs = require('querystring')
const RuleSet = require('webpack/lib/RuleSet')
const id = 'vue-loader-plugin'
const NS = 'vue-loader'
class VueLoaderPlugin {
apply (compiler) {
// add NS marker so that the loader can detect and report missing plugin
if (compiler.hooks) {
// webpack 4
compiler.hooks.compilation.tap(id, compilation => {
const normalModuleLoader = compilation.hooks.normalModuleLoader
normalModuleLoader.tap(id, loaderContext => {
loaderContext[NS] = true
})
})
} else {
// webpack < 4
compiler.plugin('compilation', compilation => {
compilation.plugin('normal-module-loader', loaderContext => {
loaderContext[NS] = true
})
})
}
// use webpack's RuleSet utility to normalize user rules
const rawRules = compiler.options.module.rules
const { rules } = new RuleSet(rawRules)
// find the rule that applies to vue files
let vueRuleIndex = rawRules.findIndex(createMatcher(`foo.vue`))
if (vueRuleIndex < 0) {
vueRuleIndex = rawRules.findIndex(createMatcher(`foo.vue.html`))
}
const vueRule = rules[vueRuleIndex]
if (!vueRule) {
throw new Error(
`[VueLoaderPlugin Error] No matching rule for .vue files found.\n` +
`Make sure there is at least one root-level rule that matches .vue or .vue.html files.`
)
}
if (vueRule.oneOf) {
throw new Error(
`[VueLoaderPlugin Error] vue-loader 15 currently does not support vue rules with oneOf.`
)
}
// get the normlized "use" for vue files
const vueUse = vueRule.use
// get vue-loader options
const vueLoaderUseIndex = vueUse.findIndex(u => {
return /^vue-loader|(\/|\\|@)vue-loader/.test(u.loader)
})
if (vueLoaderUseIndex < 0) {
throw new Error(
`[VueLoaderPlugin Error] No matching use for vue-loader is found.\n` +
`Make sure the rule matching .vue files include vue-loader in its use.`
)
}
// make sure vue-loader options has a known ident so that we can share
// options by reference in the template-loader by using a ref query like
// template-loader??vue-loader-options
const vueLoaderUse = vueUse[vueLoaderUseIndex]
vueLoaderUse.ident = 'vue-loader-options'
vueLoaderUse.options = vueLoaderUse.options || {}
// for each user rule (expect the vue rule), create a cloned rule
// that targets the corresponding language blocks in *.vue files.
const clonedRules = rules
.filter(r => r !== vueRule)
.map(cloneRule)
// global pitcher (responsible for injecting template compiler loader & CSS
// post loader)
const pitcher = {
loader: require.resolve('./loaders/pitcher'),
resourceQuery: query => {
const parsed = qs.parse(query.slice(1))
return parsed.vue != null
},
options: {
cacheDirectory: vueLoaderUse.options.cacheDirectory,
cacheIdentifier: vueLoaderUse.options.cacheIdentifier
}
}
// replace original rules
compiler.options.module.rules = [
pitcher,
...clonedRules,
...rules
]
}
}
function createMatcher (fakeFile) {
return (rule, i) => {
// #1201 we need to skip the `include` check when locating the vue rule
const clone = Object.assign({}, rule)
delete clone.include
const normalized = RuleSet.normalizeRule(clone, {}, '')
return (
!rule.enforce &&
normalized.resource &&
normalized.resource(fakeFile)
)
}
}
function cloneRule (rule) {
const { resource, resourceQuery } = rule
// Assuming `test` and `resourceQuery` tests are executed in series and
// synchronously (which is true based on RuleSet's implementation), we can
// save the current resource being matched from `test` so that we can access
// it in `resourceQuery`. This ensures when we use the normalized rule's
// resource check, include/exclude are matched correctly.
let currentResource
const res = Object.assign({}, rule, {
resource: {
test: resource => {
currentResource = resource
return true
}
},
resourceQuery: query => {
const parsed = qs.parse(query.slice(1))
if (parsed.vue == null) {
return false
}
if (resource && parsed.lang == null) {
return false
}
const fakeResourcePath = `${currentResource}.${parsed.lang}`
if (resource && !resource(fakeResourcePath)) {
return false
}
if (resourceQuery && !resourceQuery(query)) {
return false
}
return true
}
})
if (rule.oneOf) {
res.oneOf = rule.oneOf.map(cloneRule)
}
return res
}
VueLoaderPlugin.NS = NS
module.exports = VueLoaderPlugin
const qs = require('querystring')
const id = 'vue-loader-plugin'
const NS = 'vue-loader'
const BasicEffectRulePlugin = require('webpack/lib/rules/BasicEffectRulePlugin')
const BasicMatcherRulePlugin = require('webpack/lib/rules/BasicMatcherRulePlugin')
const RuleSetCompiler = require('webpack/lib/rules/RuleSetCompiler')
const UseEffectRulePlugin = require('webpack/lib/rules/UseEffectRulePlugin')
const ruleSetCompiler = new RuleSetCompiler([
new BasicMatcherRulePlugin('test', 'resource'),
new BasicMatcherRulePlugin('include', 'resource'),
new BasicMatcherRulePlugin('exclude', 'resource', true),
new BasicMatcherRulePlugin('resource'),
new BasicMatcherRulePlugin('conditions'),
new BasicMatcherRulePlugin('resourceQuery'),
new BasicMatcherRulePlugin('realResource'),
new BasicMatcherRulePlugin('issuer'),
new BasicMatcherRulePlugin('compiler'),
new BasicEffectRulePlugin('type'),
new BasicEffectRulePlugin('sideEffects'),
new BasicEffectRulePlugin('parser'),
new BasicEffectRulePlugin('resolve'),
new UseEffectRulePlugin()
])
class VueLoaderPlugin {
apply (compiler) {
// add NS marker so that the loader can detect and report missing plugin
compiler.hooks.compilation.tap(id, compilation => {
const normalModuleLoader = require('webpack/lib/NormalModule').getCompilationHooks(compilation).loader
normalModuleLoader.tap(id, loaderContext => {
loaderContext[NS] = true
})
})
const rules = compiler.options.module.rules
let rawVueRules
let vueRules = []
for (const rawRule of rules) {
// skip the `include` check when locating the vue rule
const clonedRawRule = Object.assign({}, rawRule)
delete clonedRawRule.include
const ruleSet = ruleSetCompiler.compile([{
rules: [clonedRawRule]
}])
vueRules = ruleSet.exec({
resource: 'foo.vue'
})
if (!vueRules.length) {
vueRules = ruleSet.exec({
resource: 'foo.vue.html'
})
}
if (vueRules.length > 0) {
if (rawRule.oneOf) {
throw new Error(
`[VueLoaderPlugin Error] vue-loader 15 currently does not support vue rules with oneOf.`
)
}
rawVueRules = rawRule
break
}
}
if (!vueRules.length) {
throw new Error(
`[VueLoaderPlugin Error] No matching rule for .vue files found.\n` +
`Make sure there is at least one root-level rule that matches .vue or .vue.html files.`
)
}
// get the normlized "use" for vue files
const vueUse = vueRules.filter(rule => rule.type === 'use').map(rule => rule.value)
// get vue-loader options
const vueLoaderUseIndex = vueUse.findIndex(u => {
return /^vue-loader|(\/|\\|@)vue-loader/.test(u.loader)
})
if (vueLoaderUseIndex < 0) {
throw new Error(
`[VueLoaderPlugin Error] No matching use for vue-loader is found.\n` +
`Make sure the rule matching .vue files include vue-loader in its use.`
)
}
// make sure vue-loader options has a known ident so that we can share
// options by reference in the template-loader by using a ref query like
// template-loader??vue-loader-options
const vueLoaderUse = vueUse[vueLoaderUseIndex]
vueLoaderUse.ident = 'vue-loader-options'
vueLoaderUse.options = vueLoaderUse.options || {}
// for each user rule (expect the vue rule), create a cloned rule
// that targets the corresponding language blocks in *.vue files.
const refs = new Map()
const clonedRules = rules
.filter(r => r !== rawVueRules)
.map((rawRule) => cloneRule(rawRule, refs))
// fix conflict with config.loader and config.options when using config.use
delete rawVueRules.loader
delete rawVueRules.options
rawVueRules.use = vueUse
// global pitcher (responsible for injecting template compiler loader & CSS
// post loader)
const pitcher = {
loader: require.resolve('./loaders/pitcher'),
resourceQuery: query => {
const parsed = qs.parse(query.slice(1))
return parsed.vue != null
},
options: {
cacheDirectory: vueLoaderUse.options.cacheDirectory,
cacheIdentifier: vueLoaderUse.options.cacheIdentifier
}
}
// replace original rules
compiler.options.module.rules = [
pitcher,
...clonedRules,
...rules
]
}
}
function cloneRule (rawRule, refs) {
const rules = ruleSetCompiler.compileRules('ruleSet', [{
rules: [rawRule]
}], refs)
let currentResource
const conditions = rules[0].rules
.map(rule => rule.conditions)
// shallow flat
.reduce((prev, next) => prev.concat(next), [])
// do not process rule with enforce
if (!rawRule.enforce) {
const ruleUse = rules[0].rules
.map(rule => rule.effects
.filter(effect => effect.type === 'use')
.map(effect => effect.value)
)
// shallow flat
.reduce((prev, next) => prev.concat(next), [])
// fix conflict with config.loader and config.options when using config.use
delete rawRule.loader
delete rawRule.options
rawRule.use = ruleUse
}
const res = Object.assign({}, rawRule, {
resource: resources => {
currentResource = resources
return true
},
resourceQuery: query => {
const parsed = qs.parse(query.slice(1))
if (parsed.vue == null) {
return false
}
if (!conditions) {
return false
}
const fakeResourcePath = `${currentResource}.${parsed.lang}`
for (const condition of conditions) {
// add support for resourceQuery
const request = condition.property === 'resourceQuery' ? query : fakeResourcePath
if (condition && !condition.fn(request)) {
return false
}
}
return true
}
})
delete res.test
if (rawRule.oneOf) {
res.oneOf = rawRule.oneOf.map(rule => cloneRule(rule, refs))
}
return res
}
VueLoaderPlugin.NS = NS
module.exports = VueLoaderPlugin
const qs = require('querystring')
const RuleSet = require('webpack/lib/RuleSet')
const id = 'vue-loader-plugin'
const NS = 'vue-loader'
class VueLoaderPlugin {
apply (compiler) {
// add NS marker so that the loader can detect and report missing plugin
if (compiler.hooks) {
// webpack 4
compiler.hooks.compilation.tap(id, compilation => {
let normalModuleLoader
if (Object.isFrozen(compilation.hooks)) {
// webpack 5
normalModuleLoader = require('webpack/lib/NormalModule').getCompilationHooks(compilation).loader
} else {
normalModuleLoader = compilation.hooks.normalModuleLoader
}
normalModuleLoader.tap(id, loaderContext => {
loaderContext[NS] = true
})
})
} else {
// webpack < 4
compiler.plugin('compilation', compilation => {
compilation.plugin('normal-module-loader', loaderContext => {
loaderContext[NS] = true
})
})
}
// use webpack's RuleSet utility to normalize user rules
const rawRules = compiler.options.module.rules
const { rules } = new RuleSet(rawRules)
// find the rule that applies to vue files
let vueRuleIndex = rawRules.findIndex(createMatcher(`foo.vue`))
if (vueRuleIndex < 0) {
vueRuleIndex = rawRules.findIndex(createMatcher(`foo.vue.html`))
}
const vueRule = rules[vueRuleIndex]
if (!vueRule) {
throw new Error(
`[VueLoaderPlugin Error] No matching rule for .vue files found.\n` +
`Make sure there is at least one root-level rule that matches .vue or .vue.html files.`
)
}
if (vueRule.oneOf) {
throw new Error(
`[VueLoaderPlugin Error] vue-loader 15 currently does not support vue rules with oneOf.`
)
}
// get the normlized "use" for vue files
const vueUse = vueRule.use
// get vue-loader options
const vueLoaderUseIndex = vueUse.findIndex(u => {
return /^vue-loader|(\/|\\|@)vue-loader/.test(u.loader)
})
if (vueLoaderUseIndex < 0) {
throw new Error(
`[VueLoaderPlugin Error] No matching use for vue-loader is found.\n` +
`Make sure the rule matching .vue files include vue-loader in its use.`
)
}
// make sure vue-loader options has a known ident so that we can share
// options by reference in the template-loader by using a ref query like
// template-loader??vue-loader-options
const vueLoaderUse = vueUse[vueLoaderUseIndex]
vueLoaderUse.ident = 'vue-loader-options'
vueLoaderUse.options = vueLoaderUse.options || {}
// for each user rule (expect the vue rule), create a cloned rule
// that targets the corresponding language blocks in *.vue files.
const clonedRules = rules
.filter(r => r !== vueRule)
.map(cloneRule)
// global pitcher (responsible for injecting template compiler loader & CSS
// post loader)
const pitcher = {
loader: require.resolve('./loaders/pitcher'),
resourceQuery: query => {
const parsed = qs.parse(query.slice(1))
return parsed.vue != null
},
options: {
cacheDirectory: vueLoaderUse.options.cacheDirectory,
cacheIdentifier: vueLoaderUse.options.cacheIdentifier
}
}
// replace original rules
compiler.options.module.rules = [
pitcher,
...clonedRules,
...rules
]
}
}
function createMatcher (fakeFile) {
return (rule, i) => {
// #1201 we need to skip the `include` check when locating the vue rule
const clone = Object.assign({}, rule)
delete clone.include
const normalized = RuleSet.normalizeRule(clone, {}, '')
return (
!rule.enforce &&
normalized.resource &&
normalized.resource(fakeFile)
)
}
}
function cloneRule (rule) {
const { resource, resourceQuery } = rule
// Assuming `test` and `resourceQuery` tests are executed in series and
// synchronously (which is true based on RuleSet's implementation), we can
// save the current resource being matched from `test` so that we can access
// it in `resourceQuery`. This ensures when we use the normalized rule's
// resource check, include/exclude are matched correctly.
let currentResource
const res = Object.assign({}, rule, {
resource: {
test: resource => {
currentResource = resource
return true
}
},
resourceQuery: query => {
const parsed = qs.parse(query.slice(1))
if (parsed.vue == null) {
return false
}
if (resource && parsed.lang == null) {
return false
}
const fakeResourcePath = `${currentResource}.${parsed.lang}`
if (resource && !resource(fakeResourcePath)) {
return false
}
if (resourceQuery && !resourceQuery(query)) {
return false
}
return true
}
})
if (rule.oneOf) {
res.oneOf = rule.oneOf.map(cloneRule)
}
return res
const webpack = require('webpack')
let VueLoaderPlugin = null
if (webpack.version && webpack.version[0] > 4) {
// webpack5 and upper
VueLoaderPlugin = require('./plugin-webpack5')
} else {
// webpack4 and lower
VueLoaderPlugin = require('./plugin-webpack4')
}
VueLoaderPlugin.NS = NS
module.exports = VueLoaderPlugin
{
"name": "vue-loader",
"version": "15.7.1",
"version": "15.8.3",
"description": "Vue single-file component loader for Webpack",
"main": "lib/index.js",
"typings": "lib/index.d.ts",
......@@ -34,10 +34,15 @@
},
"peerDependencies": {
"css-loader": "*",
"webpack": "^4.1.0 || ^5.0.0-0"
"webpack": "^3.0.0 || ^4.1.0 || ^5.0.0-0"
},
"peerDependenciesMeta": {
"cache-loader": {
"optional": true
}
},
"dependencies": {
"@vue/component-compiler-utils": "^3.0.0",
"@vue/component-compiler-utils": "^3.1.0",
"hash-sum": "^1.0.2",
"loader-utils": "^1.1.0",
"vue-hot-reload-api": "^2.3.0",
......
......@@ -13,5 +13,10 @@ module.exports = function(content, map) {
const resourcePath = removeExt(
normalizePath(path.relative(process.env.UNI_INPUT_DIR, this.resourcePath))
)
return content + getUsingComponentsCode(resourcePath)
content = content + getUsingComponentsCode(resourcePath)
// TODO 自动导入 vue 组件(h5,小程序,app[vue,nvue])
// 1. 需要 template-loader 解析出所有自定义组件()
// 2. 根据自定义组件信息生成引用代码
// 3. node-modules中的组件不提供自动导入
return content
}
export function parseData (data, vueComponentOptions) {
if (!data) {
return
}
const dataJson = JSON.stringify(data)
vueComponentOptions.data = function () {
return JSON.parse(dataJson)
}
}
......@@ -8912,9 +8912,9 @@ vue-style-loader@^4.1.0:
hash-sum "^1.0.2"
loader-utils "^1.0.2"
vue-template-compiler@^2.6.8:
version "2.6.10"
resolved "https://registry.npm.taobao.org/vue-template-compiler/download/vue-template-compiler-2.6.10.tgz#323b4f3495f04faa3503337a82f5d6507799c9cc"
vue-template-compiler@^2.6.11:
version "2.6.11"
resolved "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.11.tgz#c04704ef8f498b153130018993e56309d4698080"
dependencies:
de-indent "^1.0.2"
he "^1.1.0"
......@@ -8923,9 +8923,9 @@ vue-template-es2015-compiler@^1.9.0:
version "1.9.1"
resolved "https://registry.npm.taobao.org/vue-template-es2015-compiler/download/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825"
vue@^2.6.8:
version "2.6.10"
resolved "https://registry.npm.taobao.org/vue/download/vue-2.6.10.tgz#a72b1a42a4d82a721ea438d1b6bf55e66195c637"
vue@^2.6.11:
version "2.6.11"
resolved "https://registry.npmjs.org/vue/-/vue-2.6.11.tgz#76594d877d4b12234406e84e35275c6d514125c5"
w3c-hr-time@^1.0.1:
version "1.0.1"
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册