diff --git a/packages/uni-cli-shared/lib/cache.js b/packages/uni-cli-shared/lib/cache.js index 7b526016b476ef9dcafd05bea6e930996432ed72..8241a0b93f5f0b83f08c017f8d7fd344f16f0b27 100644 --- a/packages/uni-cli-shared/lib/cache.js +++ b/packages/uni-cli-shared/lib/cache.js @@ -1,14 +1,17 @@ +const fs = require('fs') +const path = require('path') +const crypto = require('crypto') /** * 1.page-loader 缓存基础的 app.json page.json project.config.json * 2.main-loader 缓存 app.json 中的 usingComponents 节点 * 3.script-loader 修改缓存 usingComponents 节点 * 5.webpack plugin 中获取被修改的 page.json,component.json 并 emitFile */ -const jsonFileMap = new Map() +let jsonFileMap = new Map() const changedJsonFileSet = new Set() -const componentSet = new Set() +let componentSet = new Set() -const pageSet = new Set() +let pageSet = new Set() let globalUsingComponents = Object.create(null) let appJsonUsingComponents = Object.create(null) @@ -228,6 +231,27 @@ function getSpecialMethods (name) { return componentSpecialMethods[name] || [] } +const pagesJsonPath = path.resolve(process.env.UNI_INPUT_DIR, 'pages.json') + +const cacheTypes = ['babel-loader', 'css-loader', 'uni-template-compiler', 'vue-loader'] + +function clearCache () { + const fsExtra = require('fs-extra') + cacheTypes.forEach(cacheType => { + fsExtra.emptyDirSync(path.resolve( + process.env.UNI_CLI_CONTEXT, + 'node_modules/.cache/' + cacheType + '/' + process.env.UNI_PLATFORM + )) + }) +} + +function digest (str) { + return crypto + .createHash('md5') + .update(str) + .digest('hex') +} + module.exports = { getPageSet () { return pageSet @@ -235,6 +259,62 @@ module.exports = { getJsonFileMap () { return jsonFileMap }, + // 先简单处理,该方案不好, + // 后续为 pages-loader 增加 cache-loader, + // 然后其他修改 json 的地方也要定制 cache-loader + store () { + const filepath = path.resolve( + process.env.UNI_CLI_CONTEXT, + 'node_modules/.cache/uni-pages-loader/' + process.env.UNI_PLATFORM, + digest(process.env.UNI_INPUT_DIR) + '.json' + ) + + const files = Array.from(jsonFileMap.entries()) + const pages = Array.from(pageSet) + const components = Array.from(componentSet) + const methods = componentSpecialMethods + fs.writeFileSync(filepath, JSON.stringify({ + mtimeMs: fs.statSync(pagesJsonPath).mtimeMs, + files, + pages, + components, + methods, + globalUsingComponents, + appJsonUsingComponents + })) + }, + restore () { + const filepath = path.resolve( + process.env.UNI_CLI_CONTEXT, + 'node_modules/.cache/uni-pages-loader/' + process.env.UNI_PLATFORM, + digest(process.env.UNI_INPUT_DIR) + '.json' + ) + if (!fs.existsSync(filepath)) { + try { + clearCache() + } catch (e) {} + return + } + const mtimeMs = fs.statSync(pagesJsonPath).mtimeMs + const jsonCache = require(filepath) + if (jsonCache.mtimeMs !== mtimeMs) { + try { + clearCache() + } catch (e) {} + return + } + jsonFileMap = new Map(jsonCache.files) + pageSet = new Set(jsonCache.pages) + componentSet = new Set(jsonCache.components) + componentSpecialMethods = jsonCache.methods + globalUsingComponents = jsonCache.globalUsingComponents + appJsonUsingComponents = jsonCache.appJsonUsingComponents + // restore 时,所有 file 均触发 change + for (let name of jsonFileMap.keys()) { + changedJsonFileSet.add(name) + } + return true + }, getJsonFile, getPagesJson, getComponentSet, diff --git a/packages/uni-template-compiler/lib/index.js b/packages/uni-template-compiler/lib/index.js index 258685bc80f77c6dfd58206661edd56a7c9a88f3..77f1fc3c2f8b281cc9114fe98ff92b7ff1753390 100644 --- a/packages/uni-template-compiler/lib/index.js +++ b/packages/uni-template-compiler/lib/index.js @@ -109,6 +109,17 @@ at ${resourcePath}.vue:1`) * ...暂时使用方案1 */ if (options.emitFile) { + // cache + if (process.env.UNI_USING_CACHE) { + const oldEmitFile = options.emitFile + process.UNI_CACHE_TEMPLATES = {} + options.emitFile = function emitFile (name, content) { + const absolutePath = path.resolve(process.env.UNI_OUTPUT_DIR, name) + process.UNI_CACHE_TEMPLATES[absolutePath] = content + oldEmitFile(name, content) + } + } + if (options.updateSpecialMethods) { options.updateSpecialMethods(resourcePath, [...res.specialMethods]) } diff --git a/packages/vue-cli-plugin-hbuilderx/build/webpack.nvue.conf.js b/packages/vue-cli-plugin-hbuilderx/build/webpack.nvue.conf.js index 49b4193e852af39dd1f0d81a946a2eac7b55cfea..f5e070a2cde716308a9bc553d79fcfd5840a1f69 100644 --- a/packages/vue-cli-plugin-hbuilderx/build/webpack.nvue.conf.js +++ b/packages/vue-cli-plugin-hbuilderx/build/webpack.nvue.conf.js @@ -96,7 +96,7 @@ const rules = [{ babelrc: false } }, - jsPreprocessorLoader + jsPreprocessorLoader ], exclude (modulePath) { return excludeModuleReg.test(modulePath) && modulePath.indexOf('@dcloudio') === -1 diff --git a/packages/vue-cli-plugin-hbuilderx/module-alias.js b/packages/vue-cli-plugin-hbuilderx/module-alias.js index 4c32e657bf06756e54db5c67e80c248a0943ea1a..385c148395f0a81b5be4e79dca624bd3824858f3 100644 --- a/packages/vue-cli-plugin-hbuilderx/module-alias.js +++ b/packages/vue-cli-plugin-hbuilderx/module-alias.js @@ -6,7 +6,7 @@ const { isInHBuilderX } = require('@dcloudio/uni-cli-shared') -// override +// 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, @@ -19,6 +19,20 @@ moduleAlias.addAlias('./templateLoader', (fromPath, request, alias) => { } return request }) +// vue cache +if ( // 非 h5 ,非 v3,非 native + process.env.UNI_PLATFORM !== 'h5' && + !process.env.UNI_USING_V3 && + !process.env.UNI_USING_NATIVE +) { + moduleAlias.addAlias('./loaders/pitcher', (fromPath, request, alias) => { + if (fromPath.indexOf('vue-loader') !== -1) { + return path.resolve(__dirname, 'packages/vue-loader/lib/loaders/pitcher') + } + return request + }) +} + if (isInHBuilderX) { moduleAlias.addAlias('typescript', path.resolve(process.env.UNI_HBUILDERX_PLUGINS, 'compile-typescript/node_modules/typescript')) diff --git a/packages/vue-cli-plugin-hbuilderx/packages/vue-loader/lib/loaders/pitcher.js b/packages/vue-cli-plugin-hbuilderx/packages/vue-loader/lib/loaders/pitcher.js new file mode 100644 index 0000000000000000000000000000000000000000..6af379ba75109d3d97a5e53b8ee5650c1cac28c0 --- /dev/null +++ b/packages/vue-cli-plugin-hbuilderx/packages/vue-loader/lib/loaders/pitcher.js @@ -0,0 +1,160 @@ +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}` +} diff --git a/packages/vue-cli-plugin-hbuilderx/packages/webpack-uni-nvue-loader/lib/template.recycle.js b/packages/vue-cli-plugin-hbuilderx/packages/webpack-uni-nvue-loader/lib/template.recycle.js index f812d34fc23a6f9c45a5ce2f725dab8943d8a20a..6395854c0692da9e6a0ac14c558bc30639ba8a19 100644 --- a/packages/vue-cli-plugin-hbuilderx/packages/webpack-uni-nvue-loader/lib/template.recycle.js +++ b/packages/vue-cli-plugin-hbuilderx/packages/webpack-uni-nvue-loader/lib/template.recycle.js @@ -2,8 +2,8 @@ const loaderUtils = require('loader-utils') module.exports = function(content) { this.cacheable && this.cacheable() - const vueLoaderOptions = this.loaders[0] - if (vueLoaderOptions.ident === 'vue-loader-options') { + const vueLoaderOptions = this.loaders.find(loader => loader.ident === 'vue-loader-options') + if (vueLoaderOptions) { const params = loaderUtils.parseQuery(this.resourceQuery) if (params.recyclable) { Object.assign(vueLoaderOptions.options.compilerOptions, { diff --git a/packages/vue-cli-plugin-uni/index.js b/packages/vue-cli-plugin-uni/index.js index b27f0efd191b0d75870648a67210aff3f1a22534..3e1a8cea95cbb5cdb87000a2d1a99102a7bb86b9 100644 --- a/packages/vue-cli-plugin-uni/index.js +++ b/packages/vue-cli-plugin-uni/index.js @@ -25,8 +25,8 @@ module.exports = (api, options) => { require('./lib/options')(options) - api.configureWebpack(require('./lib/configure-webpack')(platformOptions, manifestPlatformOptions, options)) - api.chainWebpack(require('./lib/chain-webpack')(platformOptions)) + api.configureWebpack(require('./lib/configure-webpack')(platformOptions, manifestPlatformOptions, options, api)) + api.chainWebpack(require('./lib/chain-webpack')(platformOptions, api)) } module.exports.defaultModes = { diff --git a/packages/vue-cli-plugin-uni/lib/cache-loader.js b/packages/vue-cli-plugin-uni/lib/cache-loader.js new file mode 100644 index 0000000000000000000000000000000000000000..0b659b2aeccebe54f4a626f3c71295bae4c03d6e --- /dev/null +++ b/packages/vue-cli-plugin-uni/lib/cache-loader.js @@ -0,0 +1,81 @@ +const fs = require('fs') +const path = require('path') +const mkdirp = require('mkdirp') +const BJSON = require('buffer-json') + +const { + getPartialIdentifier +} = require('./util') + +const directories = new Set() + +function write (key, data, callback) { + const dirname = path.dirname(key) + // template,缓存 mp template + if ( + data.remainingRequest.indexOf('vue&type=template') !== -1 && + process.UNI_CACHE_TEMPLATES + ) { + data['mpTemplates'] = process.UNI_CACHE_TEMPLATES + delete process.UNI_CACHE_TEMPLATES + } + + const content = BJSON.stringify(data) + + if (directories.has(dirname)) { + // for performance skip creating directory + fs.writeFile(key, content, 'utf-8', callback) + } else { + mkdirp(dirname, (mkdirErr) => { + if (mkdirErr) { + callback(mkdirErr) + return + } + + directories.add(dirname) + + fs.writeFile(key, content, 'utf-8', callback) + }) + } +} + +function read (key, callback) { + fs.readFile(key, 'utf-8', (err, content) => { + if (err) { + callback(err) + return + } + + try { + const data = BJSON.parse(content) + const mpTemplates = data['mpTemplates'] + if (mpTemplates) { + Object.keys(mpTemplates).forEach(name => { + fs.writeFileSync(name, mpTemplates[name], 'utf-8') + }) + } + callback(null, data) + } catch (e) { + callback(e) + } + }) +} + +module.exports = { + createTemplateCacheLoader: function (api) { + return { + resourceQuery: /type=uni-cache-loader-template/, + use: [{ + loader: 'cache-loader', + ident: 'uni-cache-loader-template-options', + options: Object.assign(api.genCacheConfig( + 'uni-template-compiler/' + process.env.UNI_PLATFORM, + getPartialIdentifier() + ), { + read, + write + }) + }] + } + } +} diff --git a/packages/vue-cli-plugin-uni/lib/chain-webpack.js b/packages/vue-cli-plugin-uni/lib/chain-webpack.js index 5044e042b3e62bc1a849ab31e2468ab0a08e47a2..45dc249d4b97bfba714daf2fdaff1a0dbef5ac1e 100644 --- a/packages/vue-cli-plugin-uni/lib/chain-webpack.js +++ b/packages/vue-cli-plugin-uni/lib/chain-webpack.js @@ -4,11 +4,15 @@ const { sassLoaderVersion } = require('@dcloudio/uni-cli-shared/lib/scss') +const { + getPartialIdentifier +} = require('./util') + function resolve (dir) { return path.resolve(__dirname, '..', dir) } -module.exports = function chainWebpack (platformOptions) { +module.exports = function chainWebpack (platformOptions, api) { const { runByHBuilderX, // 使用 HBuilderX 运行 cssPreprocessOptions @@ -48,11 +52,22 @@ module.exports = function chainWebpack (platformOptions) { const langRule = webpackConfig.module.rule(lang) const loader = loaders[lang] cssTypes.forEach(type => { + if (process.env.UNI_USING_CACHE) { + langRule.oneOf(type) + .use(`uniapp-cache-css`) + .loader('cache-loader') + .options(api.genCacheConfig( + 'css-loader/' + process.env.UNI_PLATFORM, + getPartialIdentifier() + )) + .before('css-loader') + } langRule.oneOf(type) .use(`uniapp-preprocss`) .loader(resolve('packages/webpack-preprocess-loader')) .options(cssPreprocessOptions) .before('css-loader') // 在 css-loader 之后条件编译一次,避免 import 进来的 css 没有走条件编译 + if (loader) { // 在 scss,less,stylus 之前先条件编译一次 langRule.oneOf(type) .use(`uniapp-preprocss-` + lang) @@ -79,7 +94,7 @@ module.exports = function chainWebpack (platformOptions) { }) } - platformOptions.chainWebpack(webpackConfig) + platformOptions.chainWebpack(webpackConfig, api) // define webpackConfig .plugin('uni-define') diff --git a/packages/vue-cli-plugin-uni/lib/configure-webpack.js b/packages/vue-cli-plugin-uni/lib/configure-webpack.js index 5910640e7fdf2bf48e4e7433b8c1a58d669f5bc0..2873411eaadfbe3adff60cd6bd9a81ee3356cf17 100644 --- a/packages/vue-cli-plugin-uni/lib/configure-webpack.js +++ b/packages/vue-cli-plugin-uni/lib/configure-webpack.js @@ -6,6 +6,10 @@ const CopyWebpackPlugin = require('copy-webpack-plugin') const merge = require('webpack-merge') +const { + getPartialIdentifier +} = require('./util') + function resolve (dir) { return path.resolve(__dirname, '..', dir) } @@ -14,7 +18,7 @@ function resolveModule (dir) { return path.resolve(__dirname, '../../..', dir) } -module.exports = function configureWebpack (platformOptions, manifestPlatformOptions, vueOptions) { +module.exports = function configureWebpack (platformOptions, manifestPlatformOptions, vueOptions, api) { const { runByHBuilderX, // 使用 HBuilderX 运行 isInHBuilderX, // 在 HBuilderX 的插件中 @@ -138,13 +142,19 @@ module.exports = function configureWebpack (platformOptions, manifestPlatformOpt return function (webpackConfig) { // disable js cache-loader const rawRules = webpackConfig.module.rules - for (let i = rawRules.length - 1; i >= 0; i--) { const uses = rawRules[i].use if (Array.isArray(uses)) { if (uses.find(use => use.loader === 'babel-loader')) { const index = uses.findIndex(use => use.loader === 'cache-loader') - uses.splice(index, 1) + if (process.env.UNI_USING_CACHE) { + Object.assign(uses[index].options, api.genCacheConfig( + 'babel-loader/' + process.env.UNI_PLATFORM, + getPartialIdentifier() + )) + } else { + uses.splice(index, 1) + } } } } @@ -168,7 +178,7 @@ module.exports = function configureWebpack (platformOptions, manifestPlatformOpt let platformWebpackConfig = platformOptions.webpackConfig if (typeof platformWebpackConfig === 'function') { - platformWebpackConfig = platformWebpackConfig(webpackConfig) + platformWebpackConfig = platformWebpackConfig(webpackConfig, api) } // 移除 node_modules 目录,避免受路径上的 node_modules 影响 webpackConfig.resolve.modules = webpackConfig.resolve.modules.filter(module => module !== diff --git a/packages/vue-cli-plugin-uni/lib/env.js b/packages/vue-cli-plugin-uni/lib/env.js index 77228ceb17be47f63182bfb3ab7e060581a222c5..f41a526cc7c1025cb1c124ddb2b3e4e2c914303f 100644 --- a/packages/vue-cli-plugin-uni/lib/env.js +++ b/packages/vue-cli-plugin-uni/lib/env.js @@ -1,4 +1,6 @@ +const fs = require('fs') const path = require('path') +const mkdirp = require('mkdirp') // 初始化环境变量 const defaultInputDir = '../../../../src' @@ -26,6 +28,10 @@ process.env.UNI_CLI_CONTEXT = path.resolve(__dirname, '../../../../') process.UNI_LIBRARIES = process.UNI_LIBRARIES || ['@dcloudio/uni-ui'] +if (process.env.NODE_ENV === 'production') { // 发行模式,不启用 cache + process.env.UNI_USING_CACHE = false +} + const { isSupportSubPackages, runByHBuilderX, @@ -115,6 +121,8 @@ if (process.env.UNI_PLATFORM === 'app-plus') { isNVueCompiler = false } if (platformOptions.renderer === 'native') { + // 纯原生目前不提供 cache + process.env.UNI_USING_CACHE = false process.env.UNI_USING_NATIVE = true process.env.UNI_USING_V8 = true process.env.UNI_OUTPUT_TMP_DIR = '' @@ -254,8 +262,25 @@ if (runByHBuilderX) { } } -console.log(`正在编译中...`) +if ( + process.env.UNI_USING_CACHE && + process.env.UNI_PLATFORM !== 'h5' && + !process.env.UNI_USING_V3 && + !process.env.UNI_USING_NATIVE +) { // 使用 cache, 拷贝 cache 的 json + const cacheJsonDir = path.resolve( + process.env.UNI_CLI_CONTEXT, + 'node_modules/.cache/uni-pages-loader/' + process.env.UNI_PLATFORM + ) + if (!fs.existsSync(cacheJsonDir)) { // 创建 cache 目录 + mkdirp(cacheJsonDir) + } else { + require('@dcloudio/uni-cli-shared/lib/cache').restore() + } +} + +runByHBuilderX && console.log(`正在编译中...`) module.exports = { manifestPlatformOptions: platformOptions -} +} diff --git a/packages/vue-cli-plugin-uni/lib/h5/index.js b/packages/vue-cli-plugin-uni/lib/h5/index.js index 9f44dd8ab8d7472e37553cc81934b000c3fefd88..db9ebde24ae18282255dccf72e6d9fd575def9b9 100644 --- a/packages/vue-cli-plugin-uni/lib/h5/index.js +++ b/packages/vue-cli-plugin-uni/lib/h5/index.js @@ -4,10 +4,11 @@ const path = require('path') const { getMainEntry, getH5Options, - getPlatformCompiler, getPlatformCssnano } = require('@dcloudio/uni-cli-shared') +const modifyVueLoader = require('../vue-loader') + const WebpackHtmlAppendPlugin = require('../../packages/webpack-html-append-plugin') function resolve (dir) { @@ -100,7 +101,7 @@ module.exports = { plugins } }, - chainWebpack (webpackConfig) { + chainWebpack (webpackConfig, api) { webpackConfig.plugins.delete('copy') if (!process.env.UNI_OPT_PREFETCH) { @@ -109,29 +110,8 @@ module.exports = { if (!process.env.UNI_OPT_PRELOAD) { webpackConfig.plugins.delete('preload-index') } - // Vue - webpackConfig.module - .rule('vue') - .test([/\.vue$/, /\.nvue$/]) - .use('vue-loader') - .tap(options => Object.assign(options, { - compiler: getPlatformCompiler(), - compilerOptions: require('./compiler-options'), - cacheDirectory: false, - cacheIdentifier: false - })) - .end() - .use('uniapp-custom-block-loader') - .loader(require.resolve('@dcloudio/vue-cli-plugin-uni/packages/webpack-custom-block-loader')) - .options({ - compiler: getPlatformCompiler() - }) - .end() - .use('uniapp-scoped') - .loader(resolve('packages/webpack-scoped-loader')) - .end() - .uses - .delete('cache-loader') + + modifyVueLoader(webpackConfig, require('./compiler-options'), api) if (process.env.NODE_ENV === 'production') { const module = webpackConfig.module diff --git a/packages/vue-cli-plugin-uni/lib/mp.js b/packages/vue-cli-plugin-uni/lib/mp.js index ad8f49d9c00d04cc635c8947b4335db240e2dd2f..662519596835a1923d6cf15fb1a4b317810ded11 100644 --- a/packages/vue-cli-plugin-uni/lib/mp.js +++ b/packages/vue-cli-plugin-uni/lib/mp.js @@ -9,13 +9,14 @@ const { parseEntry, getMainEntry, getPlatformExts, - getPlatformCompiler, getPlatformCssnano } = require('@dcloudio/uni-cli-shared') +const modifyVueLoader = require('./vue-loader') + const { - isUnaryTag -} = require('./util') + createTemplateCacheLoader +} = require('./cache-loader') function createUniMPPlugin () { if (process.env.UNI_USING_COMPONENTS) { @@ -42,7 +43,7 @@ function getProvides () { process.env.UNI_PLATFORM === 'app-plus' && process.env.UNI_USING_V8 ) { - provides['__f__'] = [path.resolve(__dirname, 'format-log.js'), 'default'] + provides['__f__'] = [path.resolve(__dirname, 'format-log.js'), 'default'] provides['crypto'] = [path.resolve(__dirname, 'crypto.js'), 'default'] } @@ -61,7 +62,7 @@ module.exports = { vueConfig: { parallel: false }, - webpackConfig (webpackConfig) { + webpackConfig (webpackConfig, api) { if (!webpackConfig.optimization) { webpackConfig.optimization = {} } @@ -105,9 +106,9 @@ module.exports = { chunkFilename: '[id].js', globalObject: process.env.UNI_PLATFORM === 'mp-alipay' ? 'my' : 'global', sourceMapFilename: '../.sourcemap/' + process.env.UNI_PLATFORM + '/[name].js.map' - }, - performance: { - hints: false + }, + performance: { + hints: false }, resolve: { extensions: ['.nvue'], @@ -132,7 +133,7 @@ module.exports = { use: [{ loader: '@dcloudio/webpack-uni-mp-loader/lib/template' }] - }, { + }, createTemplateCacheLoader(api), { resourceQuery: [ /lang=wxs/, /lang=filter/, @@ -153,7 +154,7 @@ module.exports = { ] } }, - chainWebpack (webpackConfig) { + chainWebpack (webpackConfig, api) { if (process.env.UNI_PLATFORM === 'mp-baidu') { webpackConfig.module .rule('js') @@ -161,32 +162,9 @@ module.exports = { .add(/\.filter\.js$/) } - // disable vue cache-loader - webpackConfig.module - .rule('vue') - .test([/\.vue$/, /\.nvue$/]) - .use('vue-loader') - .tap(options => Object.assign(options, { - compiler: getPlatformCompiler(), - compilerOptions: process.env.UNI_USING_COMPONENTS ? { - isUnaryTag, - preserveWhitespace: false - } : require('./mp-compiler-options'), - cacheDirectory: false, - cacheIdentifier: false - })) - .end() - .use('uniapp-custom-block-loader') - .loader(require.resolve('@dcloudio/vue-cli-plugin-uni/packages/webpack-custom-block-loader')) - .options({ - compiler: getPlatformCompiler() - }) - .end() - .use('uniapp-nvue-loader') - .loader(require.resolve('@dcloudio/webpack-uni-mp-loader/lib/style.js')) - .end() - .uses - .delete('cache-loader') + const compilerOptions = process.env.UNI_USING_COMPONENTS ? {} : require('./mp-compiler-options') + + modifyVueLoader(webpackConfig, compilerOptions, api) const styleExt = getPlatformExts().style diff --git a/packages/vue-cli-plugin-uni/lib/util.js b/packages/vue-cli-plugin-uni/lib/util.js index f2b9be2ee2f261d31ae2812ed264534433a47fa6..b0112047379ac3a77c985830752cc69909616fc3 100644 --- a/packages/vue-cli-plugin-uni/lib/util.js +++ b/packages/vue-cli-plugin-uni/lib/util.js @@ -9,9 +9,24 @@ function makeMap (str, expectsLowerCase) { : 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) { + return partialIdentifier + } + partialIdentifier = { + 'UNI_COMPILER_VERSION': require('../package.json').version + } + Object.keys(process.env).forEach(name => { + if (name.indexOf('UNI_') === 0) { + partialIdentifier[name] = process.env[name] + } + }) + return partialIdentifier + } } diff --git a/packages/vue-cli-plugin-uni/lib/vue-loader.js b/packages/vue-cli-plugin-uni/lib/vue-loader.js new file mode 100644 index 0000000000000000000000000000000000000000..e680c62367b27e0b491b1276d2e19ccd350ebc6b --- /dev/null +++ b/packages/vue-cli-plugin-uni/lib/vue-loader.js @@ -0,0 +1,71 @@ +const { + getPlatformCompiler +} = require('@dcloudio/uni-cli-shared') + +const { + isUnaryTag, + getPartialIdentifier +} = require('./util') + +module.exports = function modifyVueLoader (webpackConfig, compilerOptions, api) { + // vue-loader options + + const cacheConfig = { + cacheDirectory: false, + cacheIdentifier: false + } + const partialIdentifier = {} + + if (process.env.UNI_USING_CACHE) { + Object.assign(cacheConfig, api.genCacheConfig( + 'vue-template-compiler/' + process.env.UNI_PLATFORM, + getPartialIdentifier() + )) + } + + webpackConfig.module + .rule('vue') + .test([/\.vue$/, /\.nvue$/]) + .use('vue-loader') + .tap(options => Object.assign(options, { + compiler: getPlatformCompiler(), + compilerOptions: Object.assign({ + isUnaryTag, + preserveWhitespace: false + }, compilerOptions) + }, cacheConfig)) + .end() + .use('uniapp-custom-block-loader') + .loader(require.resolve('@dcloudio/vue-cli-plugin-uni/packages/webpack-custom-block-loader')) + .options({ + compiler: getPlatformCompiler() + }) + + // h5 框架需要使用 scoped 样式,其他平台编译时识别是否 nvue 文件且注入 flex 相关样式 + if (process.env.UNI_PLATFORM === 'h5') { + webpackConfig.module + .rule('vue') + .use('uniapp-h5-style-scoped') + .loader(require.resolve('@dcloudio/vue-cli-plugin-uni/packages/webpack-scoped-loader')) + } else { + webpackConfig.module + .rule('vue') + .use('uniapp-nvue-style-loader') + .loader(require.resolve('@dcloudio/webpack-uni-mp-loader/lib/style.js')) + } + // 是否启用 cache + if (process.env.UNI_USING_CACHE) { + webpackConfig.module + .rule('vue') + .use('cache-loader') + .tap(options => Object.assign(options, api.genCacheConfig( + 'vue-loader/' + process.env.UNI_PLATFORM, + partialIdentifier + ))) + } else { + webpackConfig.module + .rule('vue') + .uses + .delete('cache-loader') + } +} diff --git a/packages/vue-cli-plugin-uni/package.json b/packages/vue-cli-plugin-uni/package.json index 84118991135cbfbf1635b4a31a302c2ce9eed4b3..34fe2e4c80d9ef4ae873d3dc637b0fc631a691f2 100644 --- a/packages/vue-cli-plugin-uni/package.json +++ b/packages/vue-cli-plugin-uni/package.json @@ -18,11 +18,13 @@ "license": "Apache-2.0", "dependencies": { "@dcloudio/uni-stat": "^2.0.0-23320190923002", + "buffer-json": "^2.0.0", "copy-webpack-plugin": "^4.6.0", "cross-env": "^5.2.0", "envinfo": "^6.0.1", "hash-sum": "^1.0.2", "loader-utils": "^1.1.0", + "mkdirp": "^0.5.1", "module-alias": "^2.1.0", "postcss": "^7.0.7", "postcss-import": "^12.0.1", diff --git a/packages/vue-cli-plugin-uni/packages/h5-vue-template-loader/index.js b/packages/vue-cli-plugin-uni/packages/h5-vue-template-loader/index.js index 27e9fd2d6b34e6b5ee0441807d3f126aa84ad34d..837dc1b8f5af47ba2cdfb650cb82b4886ee856c7 100644 --- a/packages/vue-cli-plugin-uni/packages/h5-vue-template-loader/index.js +++ b/packages/vue-cli-plugin-uni/packages/h5-vue-template-loader/index.js @@ -5,8 +5,8 @@ const loaderUtils = require('loader-utils') module.exports = function(content) { this.cacheable && this.cacheable() - const vueLoaderOptions = this.loaders[0] - if (vueLoaderOptions.ident === 'vue-loader-options') { + const vueLoaderOptions = this.loaders.find(loader => loader.ident === 'vue-loader-options') + if (vueLoaderOptions) { const params = loaderUtils.parseQuery(this.resourceQuery) /* eslint-disable no-mixed-operators */ const filterModules = JSON.parse(params && params['filter-modules'] || '{}') diff --git a/packages/webpack-uni-mp-loader/lib/plugin/generate-json.js b/packages/webpack-uni-mp-loader/lib/plugin/generate-json.js index 148ff8454d02ac99045d6c0dc5c5fa630bce49f8..c7d7ca3d774209292fe439b2a0342dbefa0060f5 100644 --- a/packages/webpack-uni-mp-loader/lib/plugin/generate-json.js +++ b/packages/webpack-uni-mp-loader/lib/plugin/generate-json.js @@ -82,7 +82,7 @@ function analyzeUsingComponents () { // pages[name] = usingComponentsMap[name] // } // return pages - // }, {}) + // }, {}) } module.exports = function generateJson (compilation) { @@ -153,7 +153,7 @@ module.exports = function generateJson (compilation) { !['app.js', 'manifest.js', 'project.config.js', 'project.swan.js'].includes(jsFile) && !compilation.assets[jsFile] ) { - compilation.assets[jsFile] = { + const jsFileAsset = { size () { return Buffer.byteLength(EMPTY_COMPONENT, 'utf8') }, @@ -161,8 +161,9 @@ module.exports = function generateJson (compilation) { return EMPTY_COMPONENT } } + compilation.assets[jsFile] = jsFileAsset } - compilation.assets[name] = { + const jsonAsset = { size () { return Buffer.byteLength(source, 'utf8') }, @@ -170,5 +171,12 @@ module.exports = function generateJson (compilation) { return source } } + + compilation.assets[name] = jsonAsset + } + if (process.env.UNI_USING_CACHE && jsonFileMap.size) { + setTimeout(() => { + require('@dcloudio/uni-cli-shared/lib/cache').store() + }, 50) } } diff --git a/packages/webpack-uni-mp-loader/lib/template-new.js b/packages/webpack-uni-mp-loader/lib/template-new.js index 58b79b5ca11bc096b0ed15d9929a63642780201a..9801d11f1ec9854d213c4a3f58b138c539a61b27 100644 --- a/packages/webpack-uni-mp-loader/lib/template-new.js +++ b/packages/webpack-uni-mp-loader/lib/template-new.js @@ -32,8 +32,8 @@ const filterTagName = getPlatformFilterTag() || '' module.exports = function (content) { this.cacheable && this.cacheable() - const vueLoaderOptions = this.loaders[0] - if (vueLoaderOptions.ident === 'vue-loader-options') { + const vueLoaderOptions = this.loaders.find(loader => loader.ident === 'vue-loader-options') + if (vueLoaderOptions) { const globalUsingComponents = getGlobalUsingComponents() const realResourcePath = path.relative(process.env.UNI_INPUT_DIR, this.resourcePath) const resourcePath = normalizeNodeModules(removeExt(realResourcePath) + templateExt) diff --git a/packages/webpack-uni-mp-loader/lib/template.js b/packages/webpack-uni-mp-loader/lib/template.js index 017f11a07ac7745bbea4cb56c887540a019ac047..27556018f0478e4b429fde78ca157cbd567803e8 100644 --- a/packages/webpack-uni-mp-loader/lib/template.js +++ b/packages/webpack-uni-mp-loader/lib/template.js @@ -25,8 +25,8 @@ module.exports = function (content) { if (process.env.UNI_USING_COMPONENTS) { // 向 uni-template-compier 传递 emitFile - const vueLoaderOptions = this.loaders[0] - if (vueLoaderOptions.ident === 'vue-loader-options') { + const vueLoaderOptions = this.loaders.find(loader => loader.ident === 'vue-loader-options') + if (vueLoaderOptions) { Object.assign(vueLoaderOptions.options.compilerOptions, { resourcePath: removeExt(realResourcePath) + templateExt, emitFile: this.emitFile @@ -57,8 +57,8 @@ module.exports = function (content) { cacheCompilerOptions(realResourcePath, compilerOptions) // 向 vue-loader templateLoader 传递 compilerOptions - const vueLoaderOptions = this.loaders[0] - if (vueLoaderOptions.ident === 'vue-loader-options') { + const vueLoaderOptions = this.loaders.find(loader => loader.ident === 'vue-loader-options') + if (vueLoaderOptions) { Object.assign(vueLoaderOptions.options.compilerOptions, compilerOptions) } else { throw new Error('vue-loader-options parse error')