diff --git a/package.json b/package.json index 64d2288..5d61dbc 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "start": "jest test --watch", "test": "jest test", "test:webpack": "cd test && webpack", - "test:webpack:ts": "cd test && TEST_EXT=ts webpack", + "test:webpack:ts": "cd test && cross-env TEST_EXT=ts webpack", "prebuild": "rimraf ./lib && mkdirp ./lib", "build:watch": "babel src -o lib/index.js -w", "build": "babel src -o lib/index.js", @@ -40,6 +40,7 @@ "babel-plugin-transform-es2015-spread": "^6.22.0", "babel-preset-es2015": "^6.24.0", "babel-preset-stage-0": "^6.22.0", + "cross-env": "^5.2.0", "eslint": "^4.2.0", "eslint-config-standard": "^10.2.1", "eslint-loader": "^1.9.0", diff --git a/src/index.js b/src/index.js index 5125886..d271d35 100644 --- a/src/index.js +++ b/src/index.js @@ -1,445 +1,501 @@ -import { remove, readJson, existsSync, stat, readFile } from 'fs-extra'; -import { resolve, dirname, relative, join, parse } from 'path'; -import { optimize, LoaderTargetPlugin, JsonpTemplatePlugin } from 'webpack'; -import { ConcatSource } from 'webpack-sources'; -import globby from 'globby'; -import { defaults, values, uniq } from 'lodash'; -import MultiEntryPlugin from 'webpack/lib/MultiEntryPlugin'; -import SingleEntryPlugin from 'webpack/lib/SingleEntryPlugin'; -import FunctionModulePlugin from 'webpack/lib/FunctionModulePlugin'; -import NodeSourcePlugin from 'webpack/lib/node/NodeSourcePlugin'; - -const { CommonsChunkPlugin } = optimize; - -const deprecated = function deprecated(obj, key, adapter, explain) { - if (deprecated.warned.has(key)) { - return; - } - const val = obj[key]; - if (typeof val === 'undefined') { - return; - } - deprecated.warned.add(key); - adapter(val); - console.warn('[WXAppPlugin]', explain); -}; -deprecated.warned = new Set(); - -const stripExt = path => { - const { dir, name } = parse(path); - return join(dir, name); -}; - -const miniProgramTarget = compiler => { - const { options } = compiler; - compiler.apply( - new JsonpTemplatePlugin(options.output), - new FunctionModulePlugin(options.output), - new NodeSourcePlugin(options.node), - new LoaderTargetPlugin('web') - ); -}; - -export const Targets = { - Wechat(compiler) { - return miniProgramTarget(compiler); - }, - Alipay(compiler) { - return miniProgramTarget(compiler); - } -}; - -export default class WXAppPlugin { - constructor(options = {}) { - this.options = defaults(options || {}, { - clear: true, - include: [], - exclude: [], - dot: false, // Include `.dot` files - extensions: ['.js'], - commonModuleName: 'common.js', - enforceTarget: true, - assetsChunkName: '__assets_chunk_name__' - // base: undefined, - }); - - deprecated( - this.options, - 'scriptExt', - val => this.options.extensions.unshift(val), - 'Option `scriptExt` is deprecated. Please use `extensions` instead' - ); - - deprecated( - this.options, - 'forceTarge', - val => (this.options.enforceTarget = val), - 'Option `forceTarge` is deprecated. Please use `enforceTarget` instead' - ); - - this.options.extensions = uniq([...this.options.extensions, '.js']); - this.options.include = [].concat(this.options.include); - this.options.exclude = [].concat(this.options.exclude); - } - - apply(compiler) { - const { clear } = this.options; - let isFirst = true; - - this.enforceTarget(compiler); - - compiler.plugin( - 'run', - this.try(async compiler => { - await this.run(compiler); - }) - ); - - compiler.plugin( - 'watch-run', - this.try(async compiler => { - await this.run(compiler.compiler); - }) - ); - - compiler.plugin( - 'emit', - this.try(async compilation => { - if (clear && isFirst) { - isFirst = false; - await this.clear(compilation); - } - - await this.toEmitTabBarIcons(compilation); - }) - ); - - compiler.plugin( - 'after-emit', - this.try(async compilation => { - await this.toAddTabBarIconsDependencies(compilation); - }) - ); - } - - try = handler => async (arg, callback) => { - try { - await handler(arg); - callback(); - } catch (err) { - callback(err); - } - }; - - enforceTarget(compiler) { - const { enforceTarget } = this.options; - const { options } = compiler; - - if (enforceTarget) { - const { target } = options; - if (target !== Targets.Wechat && target !== Targets.Alipay) { - options.target = Targets.Wechat; - } - if (!options.node || options.node.global) { - options.node = options.node || {}; - options.node.global = false; - } - } - } - - getBase(compiler) { - const { base, extensions } = this.options; - if (base) { - return resolve(base); - } - - const { options: compilerOptions } = compiler; - const { context, entry } = compilerOptions; - - const getEntryFromCompiler = () => { - if (typeof entry === 'string') { - return entry; - } - - const extRegExpStr = extensions - .map(ext => ext.replace(/\./, '\\.')) - .map(ext => `(${ext})`) - .join('|'); - - const appJSRegExp = new RegExp(`\\bapp(${extRegExpStr})?$`); - const findAppJS = arr => arr.find(path => appJSRegExp.test(path)); - - if (Array.isArray(entry)) { - return findAppJS(entry); - } - if (typeof entry === 'object') { - for (const key in entry) { - if (!entry.hasOwnProperty(key)) { - continue; - } - - const val = entry[key]; - if (typeof val === 'string') { - return val; - } - if (Array.isArray(val)) { - return findAppJS(val); - } - } - } - }; - - const entryFromCompiler = getEntryFromCompiler(); - - if (entryFromCompiler) { - return dirname(entryFromCompiler); - } - - return context; - } - - async getTabBarIcons(tabBar) { - const tabBarIcons = new Set(); - const tabBarList = tabBar.list || []; - for (const tabBarItem of tabBarList) { - if (tabBarItem.iconPath) { - tabBarIcons.add(tabBarItem.iconPath); - } - if (tabBarItem.selectedIconPath) { - tabBarIcons.add(tabBarItem.selectedIconPath); - } - } - - this.tabBarIcons = tabBarIcons; - } - - async toEmitTabBarIcons(compilation) { - const emitIcons = []; - this.tabBarIcons.forEach(iconPath => { - const iconSrc = resolve(this.base, iconPath); - const toEmitIcon = async () => { - const iconStat = await stat(iconSrc); - const iconSource = await readFile(iconSrc); - compilation.assets[iconPath] = { - size: () => iconStat.size, - source: () => iconSource - }; - }; - emitIcons.push(toEmitIcon()); - }); - await Promise.all(emitIcons); - } - - toAddTabBarIconsDependencies(compilation) { - const { fileDependencies } = compilation; - this.tabBarIcons.forEach(iconPath => { - if (!~fileDependencies.indexOf(iconPath)) { - fileDependencies.push(iconPath); - } - }); - } - - async getEntryResource() { - const appJSONFile = resolve(this.base, 'app.json'); - const { pages = [], subPackages = [], tabBar = {} } = await readJson( - appJSONFile - ); - - const components = new Set(); - for (const page of pages) { - await this.getComponents(components, resolve(this.base, page)); - } - - for (const subPackage of subPackages) { - const { root, pages = [] } = subPackage; - - await Promise.all( - pages.map(async page => - this.getComponents(components, resolve(this.base, join(root, page))) - ) - ); - } - - this.getTabBarIcons(tabBar); - - return [ - 'app', - ...pages, - ...[].concat(...subPackages.map(v => v.pages.map(w => join(v.root, w)))), - ...components - ]; - } - - async getComponents(components, instance) { - const { usingComponents = {} } = - (await readJson(`${instance}.json`).catch( - err => err && err.code !== 'ENOENT' && console.error(err) - )) || {}; - const componentBase = parse(instance).dir; - for (const relativeComponent of values(usingComponents)) { - if (relativeComponent.indexOf('plugin://') === 0) continue; - const component = resolve(componentBase, relativeComponent); - if (!components.has(component)) { - components.add(relative(this.base, component)); - await this.getComponents(components, component); - } - } - } - - getFullScriptPath(path) { - const { - base, - options: { extensions } - } = this; - for (const ext of extensions) { - const fullPath = resolve(base, path + ext); - if (existsSync(fullPath)) { - return fullPath; - } - } - } - - async clear(compilation) { - const { path } = compilation.options.output; - await remove(path); - } - - addEntries(compiler, entries, chunkName) { - compiler.apply(new MultiEntryPlugin(this.base, entries, chunkName)); - } - - async compileAssets(compiler) { - const { - options: { include, exclude, dot, assetsChunkName, extensions }, - entryResources - } = this; - - compiler.plugin('compilation', compilation => { - compilation.plugin('before-chunk-assets', () => { - const assetsChunkIndex = compilation.chunks.findIndex( - ({ name }) => name === assetsChunkName - ); - if (assetsChunkIndex > -1) { - compilation.chunks.splice(assetsChunkIndex, 1); - } - }); - }); - - const patterns = entryResources - .map(resource => `${resource}.*`) - .concat(include); - - const entries = await globby(patterns, { - cwd: this.base, - nodir: true, - realpath: true, - ignore: [...extensions.map(ext => `**/*${ext}`), ...exclude], - dot - }); - - this.addEntries(compiler, entries, assetsChunkName); - } - - getChunkResourceRegExp() { - if (this._chunkResourceRegex) { - return this._chunkResourceRegex; - } - - const { - options: { extensions } - } = this; - const exts = extensions - .map(ext => ext.replace(/\./g, '\\.')) - .map(ext => `(${ext}$)`) - .join('|'); - return new RegExp(exts); - } - - applyCommonsChunk(compiler) { - const { - options: { commonModuleName }, - entryResources - } = this; - - const scripts = entryResources.map(::this.getFullScriptPath); - - compiler.apply( - new CommonsChunkPlugin({ - name: stripExt(commonModuleName), - minChunks: ({ resource }) => { - if (resource) { - const regExp = this.getChunkResourceRegExp(); - return regExp.test(resource) && scripts.indexOf(resource) < 0; - } - return false; - } - }) - ); - } - - addScriptEntry(compiler, entry, name) { - compiler.plugin('make', (compilation, callback) => { - const dep = SingleEntryPlugin.createDependency(entry, name); - compilation.addEntry(this.base, dep, name, callback); - }); - } - - compileScripts(compiler) { - this.applyCommonsChunk(compiler); - this.entryResources - .filter(resource => resource !== 'app') - .forEach(resource => { - const fullPath = this.getFullScriptPath(resource); - this.addScriptEntry(compiler, fullPath, resource); - }); - } - - toModifyTemplate(compilation) { - const { commonModuleName } = this.options; - const { target } = compilation.options; - const commonChunkName = stripExt(commonModuleName); - const globalVar = target.name === 'Alipay' ? 'my' : 'wx'; - - // inject chunk entries - compilation.chunkTemplate.plugin('render', (core, { name }) => { - if (this.entryResources.indexOf(name) >= 0) { - const relativePath = relative(dirname(name), `./${commonModuleName}`); - const posixPath = relativePath.replace(/\\/g, '/'); - const source = core.source(); - - // eslint-disable-next-line max-len - const injectContent = `; function webpackJsonp() { require("./${posixPath}"); ${globalVar}.webpackJsonp.apply(null, arguments); }`; - - if (source.indexOf(injectContent) < 0) { - const concatSource = new ConcatSource(core); - concatSource.add(injectContent); - return concatSource; - } - } - return core; - }); - - // replace `window` to `global` in common chunk - compilation.mainTemplate.plugin('bootstrap', (source, chunk) => { - const windowRegExp = new RegExp('window', 'g'); - if (chunk.name === commonChunkName) { - return source.replace(windowRegExp, globalVar); - } - return source; - }); - - // override `require.ensure()` - compilation.mainTemplate.plugin( - 'require-ensure', - () => 'throw new Error("Not chunk loading available");' - ); - } - - async run(compiler) { - this.base = this.getBase(compiler); - this.entryResources = await this.getEntryResource(); - compiler.plugin('compilation', ::this.toModifyTemplate); - this.compileScripts(compiler); - await this.compileAssets(compiler); - } -} +import { remove, readJson, existsSync, stat, readFile } from 'fs-extra'; +import { resolve, dirname, relative, join, parse } from 'path'; +import { optimize, LoaderTargetPlugin, JsonpTemplatePlugin } from 'webpack'; +import { ConcatSource } from 'webpack-sources'; +import globby from 'globby'; +import { defaults, values, uniq } from 'lodash'; +import MultiEntryPlugin from 'webpack/lib/MultiEntryPlugin'; +import SingleEntryPlugin from 'webpack/lib/SingleEntryPlugin'; +import FunctionModulePlugin from 'webpack/lib/FunctionModulePlugin'; +import NodeSourcePlugin from 'webpack/lib/node/NodeSourcePlugin'; + +const { CommonsChunkPlugin } = optimize; + +const deprecated = function deprecated(obj, key, adapter, explain) { + if (deprecated.warned.has(key)) { + return; + } + const val = obj[key]; + if (typeof val === 'undefined') { + return; + } + deprecated.warned.add(key); + adapter(val); + console.warn('[WXAppPlugin]', explain); +}; +deprecated.warned = new Set(); + +const stripExt = path => { + const { dir, name } = parse(path); + return join(dir, name); +}; + +const miniProgramTarget = compiler => { + const { options } = compiler; + compiler.apply( + new JsonpTemplatePlugin(options.output), + new FunctionModulePlugin(options.output), + new NodeSourcePlugin(options.node), + new LoaderTargetPlugin('web') + ); +}; + +export const Targets = { + Wechat(compiler) { + return miniProgramTarget(compiler); + }, + Alipay(compiler) { + return miniProgramTarget(compiler); + } +}; + +export default class WXAppPlugin { + constructor(options = {}) { + this.options = defaults(options || {}, { + clear: true, + include: [], + exclude: [], + dot: false, // Include `.dot` files + extensions: ['.js'], + commonModuleName: 'common.js', + enforceTarget: true, + assetsChunkName: '__assets_chunk_name__' + // base: undefined, + }); + + deprecated( + this.options, + 'scriptExt', + val => this.options.extensions.unshift(val), + 'Option `scriptExt` is deprecated. Please use `extensions` instead' + ); + + deprecated( + this.options, + 'forceTarge', + val => (this.options.enforceTarget = val), + 'Option `forceTarge` is deprecated. Please use `enforceTarget` instead' + ); + + this.options.extensions = uniq([...this.options.extensions, '.js']); + this.options.include = [].concat(this.options.include); + this.options.exclude = [].concat(this.options.exclude); + } + + apply(compiler) { + const { clear } = this.options; + let isFirst = true; + + this.enforceTarget(compiler); + + compiler.plugin( + 'run', + this.try(async compiler => { + await this.run(compiler); + }) + ); + + compiler.plugin( + 'watch-run', + this.try(async compiler => { + await this.run(compiler.compiler); + }) + ); + + compiler.plugin( + 'emit', + this.try(async compilation => { + if (clear && isFirst) { + isFirst = false; + await this.clear(compilation); + } + + await this.toEmitTabBarIcons(compilation); + }) + ); + + compiler.plugin( + 'after-emit', + this.try(async compilation => { + await this.toAddTabBarIconsDependencies(compilation); + }) + ); + } + + try = handler => async (arg, callback) => { + try { + await handler(arg); + callback(); + } catch (err) { + callback(err); + } + }; + + enforceTarget(compiler) { + const { enforceTarget } = this.options; + const { options } = compiler; + + if (enforceTarget) { + const { target } = options; + if (target !== Targets.Wechat && target !== Targets.Alipay) { + options.target = Targets.Wechat; + } + if (!options.node || options.node.global) { + options.node = options.node || {}; + options.node.global = false; + } + } + } + + getBase(compiler) { + const { base, extensions } = this.options; + if (base) { + return resolve(base); + } + + const { options: compilerOptions } = compiler; + const { context, entry } = compilerOptions; + + const getEntryFromCompiler = () => { + if (typeof entry === 'string') { + return entry; + } + + const extRegExpStr = extensions + .map(ext => ext.replace(/\./, '\\.')) + .map(ext => `(${ext})`) + .join('|'); + + const appJSRegExp = new RegExp(`\\bapp(${extRegExpStr})?$`); + const findAppJS = arr => arr.find(path => appJSRegExp.test(path)); + + if (Array.isArray(entry)) { + return findAppJS(entry); + } + if (typeof entry === 'object') { + for (const key in entry) { + if (!entry.hasOwnProperty(key)) { + continue; + } + + const val = entry[key]; + if (typeof val === 'string') { + return val; + } + if (Array.isArray(val)) { + return findAppJS(val); + } + } + } + }; + + const entryFromCompiler = getEntryFromCompiler(); + + if (entryFromCompiler) { + return dirname(entryFromCompiler); + } + + return context; + } + + getTabBarIcons(tabBar) { + const tabBarIcons = new Set(); + const tabBarList = tabBar.list || []; + for (const tabBarItem of tabBarList) { + if (tabBarItem.iconPath) { + tabBarIcons.add(tabBarItem.iconPath); + } + if (tabBarItem.selectedIconPath) { + tabBarIcons.add(tabBarItem.selectedIconPath); + } + } + + return tabBarIcons + } + + async toEmitTabBarIcons(compilation) { + const emitIcons = []; + this.tabBarIcons.forEach(iconPath => { + const iconSrc = resolve(this.base, iconPath); + const toEmitIcon = async () => { + const iconStat = await stat(iconSrc); + const iconSource = await readFile(iconSrc); + compilation.assets[iconPath] = { + size: () => iconStat.size, + source: () => iconSource + }; + }; + emitIcons.push(toEmitIcon()); + }); + await Promise.all(emitIcons); + } + + toAddTabBarIconsDependencies(compilation) { + const { fileDependencies } = compilation; + this.tabBarIcons.forEach(iconPath => { + if (!~fileDependencies.indexOf(iconPath)) { + fileDependencies.push(iconPath); + } + }); + } + + async getEntryResources(pages, subPackages) { + let subEntryResources = []; + let entryResources = [] + + const components = new Set(); + for (const page of pages) { + await this.getComponents(components, resolve(this.base, page)); + } + + for (const subPackage of subPackages) { + const { root, pages: subPages = [] } = subPackage; + + for (const subPage of subPages) { + await this.getComponents(components, resolve(this.base, join(root, subPage))); + } + + subEntryResources.push([...subPages.map(subPage => join(root, subPage))]); + } + + entryResources = ['app', ...pages, ...components] + + return { + entryResources, + subEntryResources + }; + } + + async getComponents(components, instance) { + const { usingComponents = {} } = + (await readJson(`${instance}.json`).catch( + err => err && err.code !== 'ENOENT' && console.error(err) + )) || {}; + const componentBase = parse(instance).dir; + for (const relativeComponent of values(usingComponents)) { + if (relativeComponent.indexOf('plugin://') === 0) { + continue; + } + const component = resolve(componentBase, relativeComponent); + if (!components.has(component)) { + components.add(relative(this.base, component)); + await this.getComponents(components, component); + } + } + } + + getFullScriptPath(path) { + const { + base, + options: { extensions } + } = this; + + for (const ext of extensions) { + const fullPath = resolve(base, path + ext); + if (existsSync(fullPath)) { + return fullPath; + } + } + } + + async clear(compilation) { + const { path } = compilation.options.output; + await remove(path); + } + + addEntries(compiler, entries, chunkName) { + compiler.apply(new MultiEntryPlugin(this.base, entries, chunkName)); + } + + async compileAssets(compiler) { + const { + options: { include, exclude, dot, assetsChunkName, extensions }, + entryResources, + subEntryResources + } = this; + + compiler.plugin('compilation', compilation => { + compilation.plugin('before-chunk-assets', () => { + const assetsChunkIndex = compilation.chunks.findIndex( + ({ name }) => name === assetsChunkName + ); + if (assetsChunkIndex > -1) { + compilation.chunks.splice(assetsChunkIndex, 1); + } + }); + }); + + const patterns = entryResources + .concat(...subEntryResources) + .map(resource => `${resource}.*`) + .concat(include); + + const entries = await globby(patterns, { + cwd: this.base, + nodir: true, + realpath: true, + ignore: [...extensions.map(ext => `**/*${ext}`), ...exclude], + dot + }); + + this.addEntries(compiler, entries, assetsChunkName); + } + + getChunkResourceRegExp() { + if (this._chunkResourceRegex) { + return this._chunkResourceRegex; + } + + const { + options: { extensions } + } = this; + const exts = extensions + .map(ext => ext.replace(/\./g, '\\.')) + .map(ext => `(${ext}$)`) + .join('|'); + return new RegExp(exts); + } + + applyCommonsChunk(compiler) { + const { + options: { commonModuleName }, + entryResources, + subEntryResources + } = this; + + const flatSubEntryResources = [].concat(...subEntryResources.map(v => v)); + const scripts = entryResources.concat(flatSubEntryResources).map(::this.getFullScriptPath); + + const isWin = scripts.findIndex(v => v.indexOf('\\') >= 0) !== -1; + const lastSubDirs = new Set(); + subEntryResources.forEach((pages, index) => { + if (pages.length) { + const subDir = isWin + ? pages[0].slice(0, pages[0].lastIndexOf('\\') + 1) + : pages[0].slice(0, pages[0].lastIndexOf('/') + 1); + + compiler.apply( + new CommonsChunkPlugin({ + name: stripExt(`${subDir}${commonModuleName}`), + filename: `${subDir}${commonModuleName}`, + minChunks: ({ resource }) => { + lastSubDirs.add(subDir); + + if (index === 0) { + const regExp = this.getChunkResourceRegExp(); + return resource && regExp.test(resource) && scripts.indexOf(resource) < 0; + } else { + return resource.indexOf(Array.from(lastSubDirs)[index - 1]) < 0; + } + }, + }) + ); + } + }); + + compiler.apply( + new CommonsChunkPlugin({ + name: stripExt(commonModuleName), + minChunks: ({ resource }) => { + return resource && resource.indexOf(Array.from(lastSubDirs).pop()) < 0; + } + }) + ); + } + + addScriptEntry(compiler, entry, name) { + compiler.plugin('make', (compilation, callback) => { + const dep = SingleEntryPlugin.createDependency(entry, name); + compilation.addEntry(this.base, dep, name, callback); + }); + } + + compileScripts(compiler) { + this.applyCommonsChunk(compiler); + this.entryResources + .filter(resource => resource !== 'app') + .forEach(resource => { + const fullPath = this.getFullScriptPath(resource); + this.addScriptEntry(compiler, fullPath, resource); + }); + + this.subEntryResources.forEach(item => { + item.forEach(resource => { + const fullPath = this.getFullScriptPath(resource); + this.addScriptEntry(compiler, fullPath, resource); + }); + }); + } + + toModifyTemplate(compilation) { + const { commonModuleName } = this.options; + const { target } = compilation.options; + const commonChunkName = stripExt(commonModuleName); + const globalVar = target.name === 'Alipay' ? 'my' : 'wx'; + const subEntryResources = [].concat(...this.subEntryResources.map(v => v)) + const scripts = [].concat(this.entryResources).concat(subEntryResources); + const isWin = scripts.findIndex(v => v.indexOf('\\') >= 0) !== -1; + const subDirs = this.subEntryResources + .filter(v => v.length) + .map(v => isWin + ? v[0].slice(0, v[0].lastIndexOf('\\') + 1) + : v[0].slice(0, v[0].lastIndexOf('/') + 1)); + + // inject chunk entries + compilation.chunkTemplate.plugin('render', (core, { name }) => { + if (scripts.indexOf(name) >= 0 || subDirs.find(v => name.indexOf(v) >= 0)) { + let relativePath; + if (subEntryResources.indexOf(name) >= 0) { + relativePath = `${commonModuleName}` + } else { + relativePath = relative(dirname(name), `./${commonModuleName}`); + } + + const posixPath = relativePath.replace(/\\/g, '/'); + const source = core.source(); + + // eslint-disable-next-line max-len + const injectContent = `; function webpackJsonp() { require("./${posixPath}"); ${globalVar}.webpackJsonp.apply(null, arguments); }`; + + if (source.indexOf(injectContent) < 0) { + const concatSource = new ConcatSource(core); + concatSource.add(injectContent); + return concatSource; + } + } + return core; + }); + + // replace `window` to `global` in common chunk + compilation.mainTemplate.plugin('bootstrap', (source, chunk) => { + const windowRegExp = new RegExp('window', 'g'); + if (chunk.name === commonChunkName) { + return source.replace(windowRegExp, globalVar); + } + return source; + }); + + // override `require.ensure()` + compilation.mainTemplate.plugin( + 'require-ensure', + () => 'throw new Error("Not chunk loading available");' + ); + } + + async run(compiler) { + this.base = this.getBase(compiler); + + const appJSONFile = resolve(this.base, 'app.json'); + const { pages = [], subPackages = [], tabBar = {} } = await readJson(appJSONFile); + + this.tabBarIcons = this.getTabBarIcons(tabBar); + + const { entryResources, subEntryResources } = await this.getEntryResources(pages, subPackages); + this.entryResources = entryResources; + this.subEntryResources = subEntryResources; + + compiler.plugin('compilation', ::this.toModifyTemplate); + this.compileScripts(compiler); + await this.compileAssets(compiler); + } +} diff --git a/test/src/js/app.json b/test/src/js/app.json index 12ffc49..322e569 100644 --- a/test/src/js/app.json +++ b/test/src/js/app.json @@ -23,6 +23,13 @@ "productDetail", "productList" ] + }, + { + "root": "pages/product2", + "pages": [ + "productDetail", + "productList" + ] } ], "window":{ diff --git a/test/src/js/pages/index/index.js b/test/src/js/pages/index/index.js index 0b52d56..1f501e5 100644 --- a/test/src/js/pages/index/index.js +++ b/test/src/js/pages/index/index.js @@ -9,21 +9,19 @@ const app = getApp(); // eslint-disable-line no-undef Page({ data: { motto: 'Hello World', - userInfo: {}, }, - //事件处理函数 - bindViewTap() { + goToSubList1() { wx.navigateTo({ - url: '../logs/logs', + url: '../product/productList', }); }, - goToSubList() { + goToSubList2() { wx.navigateTo({ - url: '../product/productList', + url: '../product2/productList', }); }, onLoad() { - + console.log(1) // await delay(); // const log = flow(() => { @@ -31,11 +29,5 @@ Page({ // }); // log(); - - //调用应用实例的方法获取全局数据 - app.getUserInfo((userInfo) => { - //更新数据 - this.setData({ userInfo }); - }); }, }); diff --git a/test/src/js/pages/index/index.wxml b/test/src/js/pages/index/index.wxml index 6fa58f8..7af10b9 100644 --- a/test/src/js/pages/index/index.wxml +++ b/test/src/js/pages/index/index.wxml @@ -1,11 +1,10 @@ - - - {{userInfo.nickName}} - + + {{motto}} - go to subPackages + go to subPackages1 + go to subPackages2 diff --git a/test/src/js/pages/product/product.service.js b/test/src/js/pages/product/product.service.js new file mode 100644 index 0000000..e37d21f --- /dev/null +++ b/test/src/js/pages/product/product.service.js @@ -0,0 +1,9 @@ +export default class Product { + constructor() { + this.name = 'CAR' + } + + getProductName() { + return this.name + } +} \ No newline at end of file diff --git a/test/src/js/pages/product/productDetail.js b/test/src/js/pages/product/productDetail.js index b349c0b..a8b3093 100644 --- a/test/src/js/pages/product/productDetail.js +++ b/test/src/js/pages/product/productDetail.js @@ -1,5 +1,7 @@ import { formatTime } from '../../utils/util'; +import Product from './product.service'; +const productService = new Product(); Page({ data: { @@ -10,6 +12,7 @@ Page({ logs: (wx.getStorageSync('logs') || []).map(function (log) { return formatTime(new Date(log)); }), + productName: productService.getProductName() }); } }); diff --git a/test/src/js/pages/product/productDetail.json b/test/src/js/pages/product/productDetail.json index 69bae8d..7bf6b30 100644 --- a/test/src/js/pages/product/productDetail.json +++ b/test/src/js/pages/product/productDetail.json @@ -1,5 +1,5 @@ { - "navigationBarTitleText": "分包明细", + "navigationBarTitleText": "分包明细1", "usingComponents": { "log-component": "../../components/log-component/log-component" } diff --git a/test/src/js/pages/product/productDetail.wxml b/test/src/js/pages/product/productDetail.wxml index 0e2e86f..137fbe3 100644 --- a/test/src/js/pages/product/productDetail.wxml +++ b/test/src/js/pages/product/productDetail.wxml @@ -1,6 +1,6 @@ - {{index + 1}}. {{log}} + {{index + 1}}. {{log}} {{productName}} diff --git a/test/src/js/pages/product/productList.js b/test/src/js/pages/product/productList.js index f927edb..3b33532 100644 --- a/test/src/js/pages/product/productList.js +++ b/test/src/js/pages/product/productList.js @@ -2,7 +2,8 @@ // import { flow } from 'lodash'; // const delay = (t = 0) => new Promise((resolve) => setTimeout(resolve, t)); - +import Product from './product.service'; +const productService = new Product(); //获取应用实例 const app = getApp(); // eslint-disable-line no-undef @@ -26,11 +27,8 @@ Page({ // }); // log(); - - //调用应用实例的方法获取全局数据 - app.getUserInfo((userInfo) => { - //更新数据 - this.setData({ userInfo }); - }); + this.setData({ + productName: productService.getProductName() + }) }, }); diff --git a/test/src/js/pages/product/productList.json b/test/src/js/pages/product/productList.json index 19af1ed..a8f0ff9 100644 --- a/test/src/js/pages/product/productList.json +++ b/test/src/js/pages/product/productList.json @@ -1,6 +1,7 @@ { - "usingComponents": { - "index-component": "../../components/index-component/index-component" - } + "navigationBarTitleText": "分包列表1", + "usingComponents": { + "index-component": "../../components/index-component/index-component" } +} \ No newline at end of file diff --git a/test/src/js/pages/product/productList.wxml b/test/src/js/pages/product/productList.wxml index 7c35a94..5ae5509 100644 --- a/test/src/js/pages/product/productList.wxml +++ b/test/src/js/pages/product/productList.wxml @@ -1,35 +1,11 @@ - - click me and go to detail page - - {{userInfo.nickName}} - - - {{motto}} - - - - - - - click me and go to detail page - - {{userInfo.nickName}} - - {{motto}} + {{motto}} productName is {{productName}} - - - - - + click me and go to detail page - - {{userInfo.nickName}} - - - {{motto}} + + diff --git a/test/src/js/pages/product2/product.service.js b/test/src/js/pages/product2/product.service.js new file mode 100644 index 0000000..e37d21f --- /dev/null +++ b/test/src/js/pages/product2/product.service.js @@ -0,0 +1,9 @@ +export default class Product { + constructor() { + this.name = 'CAR' + } + + getProductName() { + return this.name + } +} \ No newline at end of file diff --git a/test/src/js/pages/product2/productDetail.js b/test/src/js/pages/product2/productDetail.js new file mode 100644 index 0000000..a8b3093 --- /dev/null +++ b/test/src/js/pages/product2/productDetail.js @@ -0,0 +1,18 @@ + +import { formatTime } from '../../utils/util'; +import Product from './product.service'; +const productService = new Product(); + +Page({ + data: { + logs: [], + }, + onLoad() { + this.setData({ + logs: (wx.getStorageSync('logs') || []).map(function (log) { + return formatTime(new Date(log)); + }), + productName: productService.getProductName() + }); + } +}); diff --git a/test/src/js/pages/product2/productDetail.json b/test/src/js/pages/product2/productDetail.json new file mode 100644 index 0000000..d6959de --- /dev/null +++ b/test/src/js/pages/product2/productDetail.json @@ -0,0 +1,7 @@ +{ + "navigationBarTitleText": "分包明细2", + "usingComponents": { + "log-component": "../../components/log-component/log-component" + } + } + \ No newline at end of file diff --git a/test/src/js/pages/product2/productDetail.wxml b/test/src/js/pages/product2/productDetail.wxml new file mode 100644 index 0000000..137fbe3 --- /dev/null +++ b/test/src/js/pages/product2/productDetail.wxml @@ -0,0 +1,6 @@ + + + + {{index + 1}}. {{log}} {{productName}} + + diff --git a/test/src/js/pages/product2/productDetail.wxss b/test/src/js/pages/product2/productDetail.wxss new file mode 100644 index 0000000..e69de29 diff --git a/test/src/js/pages/product2/productList.js b/test/src/js/pages/product2/productList.js new file mode 100644 index 0000000..3b33532 --- /dev/null +++ b/test/src/js/pages/product2/productList.js @@ -0,0 +1,34 @@ + +// import { flow } from 'lodash'; + +// const delay = (t = 0) => new Promise((resolve) => setTimeout(resolve, t)); +import Product from './product.service'; +const productService = new Product(); +//获取应用实例 +const app = getApp(); // eslint-disable-line no-undef + +Page({ + data: { + motto: 'Hello List', + userInfo: {}, + }, + //事件处理函数 + bindViewTap() { + wx.navigateTo({ + url: './productDetail', + }); + }, + onLoad() { + + // await delay(); + + // const log = flow(() => { + // console.log('onLoad'); + // }); + + // log(); + this.setData({ + productName: productService.getProductName() + }) + }, +}); diff --git a/test/src/js/pages/product2/productList.json b/test/src/js/pages/product2/productList.json new file mode 100644 index 0000000..828eb8d --- /dev/null +++ b/test/src/js/pages/product2/productList.json @@ -0,0 +1,6 @@ +{ + "navigationBarTitleText": "分包列表2", + "usingComponents": { + "index-component": "../../components/index-component/index-component" + } +} \ No newline at end of file diff --git a/test/src/js/pages/product2/productList.wxml b/test/src/js/pages/product2/productList.wxml new file mode 100644 index 0000000..c2348bf --- /dev/null +++ b/test/src/js/pages/product2/productList.wxml @@ -0,0 +1,11 @@ + + + + {{motto}} + + + click me and go to detail page + + + + diff --git a/test/src/js/pages/product2/productList.wxss b/test/src/js/pages/product2/productList.wxss new file mode 100644 index 0000000..e69de29 diff --git a/test/src/ts/app.json b/test/src/ts/app.json index 12ffc49..322e569 100644 --- a/test/src/ts/app.json +++ b/test/src/ts/app.json @@ -23,6 +23,13 @@ "productDetail", "productList" ] + }, + { + "root": "pages/product2", + "pages": [ + "productDetail", + "productList" + ] } ], "window":{ diff --git a/test/src/ts/pages/index/index.ts b/test/src/ts/pages/index/index.ts index 0b52d56..023155e 100644 --- a/test/src/ts/pages/index/index.ts +++ b/test/src/ts/pages/index/index.ts @@ -9,17 +9,15 @@ const app = getApp(); // eslint-disable-line no-undef Page({ data: { motto: 'Hello World', - userInfo: {}, }, - //事件处理函数 - bindViewTap() { + goToSubList1() { wx.navigateTo({ - url: '../logs/logs', + url: '../product/productList', }); }, - goToSubList() { + goToSubList2() { wx.navigateTo({ - url: '../product/productList', + url: '../product2/productList', }); }, onLoad() { @@ -31,11 +29,5 @@ Page({ // }); // log(); - - //调用应用实例的方法获取全局数据 - app.getUserInfo((userInfo) => { - //更新数据 - this.setData({ userInfo }); - }); }, }); diff --git a/test/src/ts/pages/index/index.wxml b/test/src/ts/pages/index/index.wxml index 6fa58f8..7af10b9 100644 --- a/test/src/ts/pages/index/index.wxml +++ b/test/src/ts/pages/index/index.wxml @@ -1,11 +1,10 @@ - - - {{userInfo.nickName}} - + + {{motto}} - go to subPackages + go to subPackages1 + go to subPackages2 diff --git a/test/src/ts/pages/product/product.service.ts b/test/src/ts/pages/product/product.service.ts new file mode 100644 index 0000000..e37d21f --- /dev/null +++ b/test/src/ts/pages/product/product.service.ts @@ -0,0 +1,9 @@ +export default class Product { + constructor() { + this.name = 'CAR' + } + + getProductName() { + return this.name + } +} \ No newline at end of file diff --git a/test/src/ts/pages/product/productDetail.json b/test/src/ts/pages/product/productDetail.json index 69bae8d..7bf6b30 100644 --- a/test/src/ts/pages/product/productDetail.json +++ b/test/src/ts/pages/product/productDetail.json @@ -1,5 +1,5 @@ { - "navigationBarTitleText": "分包明细", + "navigationBarTitleText": "分包明细1", "usingComponents": { "log-component": "../../components/log-component/log-component" } diff --git a/test/src/ts/pages/product/productDetail.ts b/test/src/ts/pages/product/productDetail.ts index b349c0b..a8b3093 100644 --- a/test/src/ts/pages/product/productDetail.ts +++ b/test/src/ts/pages/product/productDetail.ts @@ -1,5 +1,7 @@ import { formatTime } from '../../utils/util'; +import Product from './product.service'; +const productService = new Product(); Page({ data: { @@ -10,6 +12,7 @@ Page({ logs: (wx.getStorageSync('logs') || []).map(function (log) { return formatTime(new Date(log)); }), + productName: productService.getProductName() }); } }); diff --git a/test/src/ts/pages/product/productDetail.wxml b/test/src/ts/pages/product/productDetail.wxml index 0e2e86f..137fbe3 100644 --- a/test/src/ts/pages/product/productDetail.wxml +++ b/test/src/ts/pages/product/productDetail.wxml @@ -1,6 +1,6 @@ - {{index + 1}}. {{log}} + {{index + 1}}. {{log}} {{productName}} diff --git a/test/src/ts/pages/product/productList.json b/test/src/ts/pages/product/productList.json index 19af1ed..a8f0ff9 100644 --- a/test/src/ts/pages/product/productList.json +++ b/test/src/ts/pages/product/productList.json @@ -1,6 +1,7 @@ { - "usingComponents": { - "index-component": "../../components/index-component/index-component" - } + "navigationBarTitleText": "分包列表1", + "usingComponents": { + "index-component": "../../components/index-component/index-component" } +} \ No newline at end of file diff --git a/test/src/ts/pages/product/productList.ts b/test/src/ts/pages/product/productList.ts index f927edb..3b33532 100644 --- a/test/src/ts/pages/product/productList.ts +++ b/test/src/ts/pages/product/productList.ts @@ -2,7 +2,8 @@ // import { flow } from 'lodash'; // const delay = (t = 0) => new Promise((resolve) => setTimeout(resolve, t)); - +import Product from './product.service'; +const productService = new Product(); //获取应用实例 const app = getApp(); // eslint-disable-line no-undef @@ -26,11 +27,8 @@ Page({ // }); // log(); - - //调用应用实例的方法获取全局数据 - app.getUserInfo((userInfo) => { - //更新数据 - this.setData({ userInfo }); - }); + this.setData({ + productName: productService.getProductName() + }) }, }); diff --git a/test/src/ts/pages/product/productList.wxml b/test/src/ts/pages/product/productList.wxml index 7c35a94..5ae5509 100644 --- a/test/src/ts/pages/product/productList.wxml +++ b/test/src/ts/pages/product/productList.wxml @@ -1,35 +1,11 @@ - - click me and go to detail page - - {{userInfo.nickName}} - - - {{motto}} - - - - - - - click me and go to detail page - - {{userInfo.nickName}} - - {{motto}} + {{motto}} productName is {{productName}} - - - - - + click me and go to detail page - - {{userInfo.nickName}} - - - {{motto}} + + diff --git a/test/src/ts/pages/product2/product.service.ts b/test/src/ts/pages/product2/product.service.ts new file mode 100644 index 0000000..e37d21f --- /dev/null +++ b/test/src/ts/pages/product2/product.service.ts @@ -0,0 +1,9 @@ +export default class Product { + constructor() { + this.name = 'CAR' + } + + getProductName() { + return this.name + } +} \ No newline at end of file diff --git a/test/src/ts/pages/product2/productDetail.json b/test/src/ts/pages/product2/productDetail.json new file mode 100644 index 0000000..d6959de --- /dev/null +++ b/test/src/ts/pages/product2/productDetail.json @@ -0,0 +1,7 @@ +{ + "navigationBarTitleText": "分包明细2", + "usingComponents": { + "log-component": "../../components/log-component/log-component" + } + } + \ No newline at end of file diff --git a/test/src/ts/pages/product2/productDetail.ts b/test/src/ts/pages/product2/productDetail.ts new file mode 100644 index 0000000..a8b3093 --- /dev/null +++ b/test/src/ts/pages/product2/productDetail.ts @@ -0,0 +1,18 @@ + +import { formatTime } from '../../utils/util'; +import Product from './product.service'; +const productService = new Product(); + +Page({ + data: { + logs: [], + }, + onLoad() { + this.setData({ + logs: (wx.getStorageSync('logs') || []).map(function (log) { + return formatTime(new Date(log)); + }), + productName: productService.getProductName() + }); + } +}); diff --git a/test/src/ts/pages/product2/productDetail.wxml b/test/src/ts/pages/product2/productDetail.wxml new file mode 100644 index 0000000..137fbe3 --- /dev/null +++ b/test/src/ts/pages/product2/productDetail.wxml @@ -0,0 +1,6 @@ + + + + {{index + 1}}. {{log}} {{productName}} + + diff --git a/test/src/ts/pages/product2/productDetail.wxss b/test/src/ts/pages/product2/productDetail.wxss new file mode 100644 index 0000000..e69de29 diff --git a/test/src/ts/pages/product2/productList.json b/test/src/ts/pages/product2/productList.json new file mode 100644 index 0000000..828eb8d --- /dev/null +++ b/test/src/ts/pages/product2/productList.json @@ -0,0 +1,6 @@ +{ + "navigationBarTitleText": "分包列表2", + "usingComponents": { + "index-component": "../../components/index-component/index-component" + } +} \ No newline at end of file diff --git a/test/src/ts/pages/product2/productList.ts b/test/src/ts/pages/product2/productList.ts new file mode 100644 index 0000000..3b33532 --- /dev/null +++ b/test/src/ts/pages/product2/productList.ts @@ -0,0 +1,34 @@ + +// import { flow } from 'lodash'; + +// const delay = (t = 0) => new Promise((resolve) => setTimeout(resolve, t)); +import Product from './product.service'; +const productService = new Product(); +//获取应用实例 +const app = getApp(); // eslint-disable-line no-undef + +Page({ + data: { + motto: 'Hello List', + userInfo: {}, + }, + //事件处理函数 + bindViewTap() { + wx.navigateTo({ + url: './productDetail', + }); + }, + onLoad() { + + // await delay(); + + // const log = flow(() => { + // console.log('onLoad'); + // }); + + // log(); + this.setData({ + productName: productService.getProductName() + }) + }, +}); diff --git a/test/src/ts/pages/product2/productList.wxml b/test/src/ts/pages/product2/productList.wxml new file mode 100644 index 0000000..c2348bf --- /dev/null +++ b/test/src/ts/pages/product2/productList.wxml @@ -0,0 +1,11 @@ + + + + {{motto}} + + + click me and go to detail page + + + + diff --git a/test/src/ts/pages/product2/productList.wxss b/test/src/ts/pages/product2/productList.wxss new file mode 100644 index 0000000..e69de29 diff --git a/test/test.js b/test/test.js index 5b666b2..18c96c7 100644 --- a/test/test.js +++ b/test/test.js @@ -1,4 +1,3 @@ - import rimraf from 'rimraf'; import { execSync } from 'child_process'; import { resolve } from 'path'; @@ -14,10 +13,15 @@ const createTest = function createTest(ext) { TEST_EXT: ext, }, }); - stdout && console.log(stdout); - } - catch (err) { - err.stdout && console.error(err.stdout); + + if (stdout) { + console.log(stdout); + } + } catch (err) { + if (err.stdout) { + console.error(err.stdout); + } + expect(err).toBe(undefined); } @@ -29,11 +33,20 @@ const createTest = function createTest(ext) { require(`./dist/${ext}/app`); require(`./dist/${ext}/pages/index/index`); require(`./dist/${ext}/pages/logs/logs`); + require(`./dist/${ext}/pages/product/productDetail`); + require(`./dist/${ext}/pages/product/productList`); + require(`./dist/${ext}/pages/product2/productDetail`); + require(`./dist/${ext}/pages/product2/productList`); expect(global.App.mock.calls.length).toBe(1); - expect(global.Page.mock.calls.length).toBe(2); + expect(global.Page.mock.calls.length).toBe(6); + + const getVendorPath = path => resolve(__dirname, path, 'common.js'); + expect(existsSync(getVendorPath(`dist/${ext}/pages/product`))).toBe(true); + expect(existsSync(getVendorPath(`dist/${ext}/pages/product2`))).toBe(true); + expect(existsSync(getVendorPath(`dist/${ext}`))).toBe(true); - const inImagesDir = (name) => resolve(__dirname, `dist/${ext}/images`, name); + const inImagesDir = name => resolve(__dirname, `dist/${ext}/images`, name); expect(existsSync(inImagesDir('wechat.png'))).toBe(true); expect(existsSync(inImagesDir('wechat_selected.png'))).toBe(true); expect(existsSync(inImagesDir('twitter.png'))).toBe(true);