const fs = require('fs') const path = require('path') const webpack = require('webpack') const RuleSet = require('webpack/lib/RuleSet') const CopyWebpackPlugin = require('copy-webpack-plugin') const merge = require('webpack-merge') const { getPartialIdentifier } = require('./util') function resolve (dir) { return path.resolve(__dirname, '..', dir) } function resolveModule (dir) { return path.resolve(process.env.UNI_CLI_CONTEXT, './node_modules', dir) } module.exports = function configureWebpack (platformOptions, manifestPlatformOptions, vueOptions, api) { const { runByHBuilderX, // 使用 HBuilderX 运行 isInHBuilderX, // 在 HBuilderX 的插件中 hasModule, jsPreprocessOptions, htmlPreprocessOptions } = require('@dcloudio/uni-cli-shared') const { getPlatformVue } = require('@dcloudio/uni-cli-shared/lib/platform') const { getCopyWebpackPluginOptions } = require('./copy-webpack-options') function createMatcher (fakeFile) { return (rule, i) => { const clone = Object.assign({}, rule) delete clone.include const normalized = RuleSet.normalizeRule(clone, {}, '') return ( !rule.enforce && normalized.resource && normalized.resource(fakeFile) ) } } function updateJsLoader (rawRules, fakeFile, checkLoaderRegex, loader) { const matchRule = rawRules.find(createMatcher(fakeFile)) const matchUse = matchRule.use const matchLoaderUseIndex = matchUse.findIndex(u => { return checkLoaderRegex.test(u.loader) }) if (matchLoaderUseIndex < 0) { throw new Error(`No matching use for ${fakeFile}`) } matchUse.push(loader) } const userTsConfigJson = path.resolve(process.env.UNI_INPUT_DIR, 'tsconfig.json') const defaultTsConfigJson = path.resolve(process.env.UNI_CLI_CONTEXT, 'tsconfig.json') const tsConfigJsonFile = fs.existsSync(userTsConfigJson) ? userTsConfigJson : defaultTsConfigJson const context = isInHBuilderX ? process.env.UNI_INPUT_DIR : process.env.UNI_CLI_CONTEXT const tsLoaderOptions = { context, configFile: tsConfigJsonFile, transpileOnly: false, compilerOptions: { baseUrl: context, typeRoots: [resolveModule('@dcloudio/types'), resolveModule('@types')], types: [ 'uni-app', 'webpack-env' ], paths: { '@/*': [ path.join(process.env.UNI_INPUT_DIR, '*') ], vue: [ resolveModule('vue') ], vuex: [ resolveModule('vuex') ], 'vue-class-component': [ resolveModule('vue-class-component') ], 'vue-property-decorator': [ resolveModule('vue-property-decorator') ], tslib: [ resolveModule('tslib') ], 'mpvue-page-factory': [ resolveModule('@dcloudio/vue-cli-plugin-uni/packages/mpvue-page-factory') ] } }, errorFormatter (error, colors) { const messageColor = error.severity === 'warning' ? colors.bold.yellow : colors.bold.red const filePath = path.relative(process.env.UNI_INPUT_DIR, error.file).replace('.vue.ts', '.vue') if (error.code === 2307 && error.content.includes('.vue')) { error.content = error.content.replace('Cannot find module ', '') + ' script 节点必须使用 lang="ts",文档参考地址:https://uniapp.dcloud.io/frame?id=vue-ts' } return messageColor( `[tsl] ERROR at ${filePath}:${error.line} TS${error.code}:${error.content}` ) } } function updateTsLoader (rawRules, fakeFile, loader) { const matchRule = rawRules.find(createMatcher(fakeFile)) if (matchRule && matchRule.use) { if (runByHBuilderX) { matchRule.use.forEach(matchUse => { if (matchUse.loader.includes('ts-loader')) { Object.assign(matchUse.options, tsLoaderOptions) } }) } matchRule.use.push(loader) } } function removeForkTsCheckerWebpackPlugin (rawPlugins) { if (isInHBuilderX && hasModule('fork-ts-checker-webpack-plugin')) { const pluginIndex = rawPlugins.findIndex(rawPlugin => rawPlugin.vue && rawPlugin.typescriptVersion) if (pluginIndex !== -1) { // 移除fork-ts-checker-webpack-plugin rawPlugins.splice(pluginIndex, 1) } } } const babelLoaderRe = /^babel-loader|(\/|\\|@)babel-loader/ const cacheLoaderRe = /^cache-loader|(\/|\\|@)cache-loader/ 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 => babelLoaderRe.test(use.loader))) { const index = uses.findIndex(use => cacheLoaderRe.test(use.loader)) 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) } } } } // js preprocess updateJsLoader(rawRules, 'foo.js', babelLoaderRe, { loader: resolve('packages/webpack-preprocess-loader'), options: jsPreprocessOptions }) // ts options and preprocess updateTsLoader(rawRules, 'foo.ts', { loader: resolve('packages/webpack-preprocess-loader'), options: jsPreprocessOptions }) updateTsLoader(rawRules, 'foo.tsx', { loader: resolve('packages/webpack-preprocess-loader'), options: jsPreprocessOptions }) // 如果在 HBuilderX 中 removeForkTsCheckerWebpackPlugin(webpackConfig.plugins) let platformWebpackConfig = platformOptions.webpackConfig if (typeof platformWebpackConfig === 'function') { platformWebpackConfig = platformWebpackConfig(webpackConfig, vueOptions, api) } // 移除 node_modules 目录,避免受路径上的 node_modules 影响 if (require('@dcloudio/uni-cli-shared/lib/util').isInHBuilderX) { webpackConfig.resolve.modules = webpackConfig.resolve.modules.filter(module => module !== 'node_modules') } const plugins = [] const isAppView = process.env.UNI_PLATFORM === 'app-plus' && vueOptions.pluginOptions && vueOptions.pluginOptions['uni-app-plus'] && vueOptions.pluginOptions['uni-app-plus'].view if (!isAppView) { // app-plus view不需要copy plugins.push(new CopyWebpackPlugin(getCopyWebpackPluginOptions(manifestPlatformOptions, vueOptions))) } if (!process.env.UNI_SUBPACKGE || !process.env.UNI_MP_PLUGIN) { try { const automatorJson = require.resolve('@dcloudio/uni-automator/dist/automator.json') plugins.push(new CopyWebpackPlugin([{ from: automatorJson, to: '../.automator/' + (process.env.UNI_SUB_PLATFORM || process.env.UNI_PLATFORM) + '/.automator.json', transform (content) { if (process.env.UNI_AUTOMATOR_WS_ENDPOINT) { return JSON.stringify({ version: require('@dcloudio/uni-automator/package.json').version, wsEndpoint: process.env.UNI_AUTOMATOR_WS_ENDPOINT }) } return '' } }])) } catch (e) {} } if (process.UNI_SCRIPT_ENV && Object.keys(process.UNI_SCRIPT_ENV).length) { // custom define const envs = Object.create(null) Object.keys(process.UNI_SCRIPT_ENV).forEach(name => { envs['process.env.' + name] = JSON.stringify(process.UNI_SCRIPT_ENV[name]) }) plugins.push(new webpack.DefinePlugin(envs)) } if (runByHBuilderX) { // 使用 HBuilderX 中运行时,调整错误日志输出 const WebpackErrorsPlugin = require('../packages/webpack-errors-plugin') const onErrors = require('../util/on-errors') const onWarnings = require('../util/on-warnings') plugins.push(new WebpackErrorsPlugin({ onErrors, onWarnings })) } const rules = [{ test: path.resolve(process.env.UNI_INPUT_DIR, 'pages.json'), use: [{ loader: 'babel-loader' }, { loader: '@dcloudio/webpack-uni-pages-loader' }], type: 'javascript/auto' }, { resourceQuery: /vue&type=template/, use: [{ loader: resolve('packages/webpack-preprocess-loader'), options: htmlPreprocessOptions }] } ] if (!process.env.UNI_USING_COMPONENTS) { // 新版本,在 script-loader 中处理(为了避免 babel generator 移除部分条件编译代码) rules.push({ resourceQuery: /vue&type=script/, use: [{ loader: resolve('packages/webpack-preprocess-loader'), options: jsPreprocessOptions }] }) } if (process.env.NODE_ENV === 'development') { const sourceMap = require('@dcloudio/uni-cli-shared/lib/source-map') let isAppService = false if ( process.env.UNI_PLATFORM === 'app-plus' && vueOptions.pluginOptions && vueOptions.pluginOptions['uni-app-plus'] ) { isAppService = !!vueOptions.pluginOptions['uni-app-plus'].service } if (process.env.UNI_PLATFORM === 'h5' || isAppService) { plugins.push(sourceMap.createEvalSourceMapDevToolPlugin()) } else if ( process.env.UNI_PLATFORM.indexOf('mp-') === 0 && process.env.UNI_PLATFORM !== 'mp-baidu' && process.env.UNI_PLATFORM !== 'mp-alipay' && process.env.UNI_PLATFORM !== 'quickapp-webview' // 目前 ov 的开发工具支持 eval 模式 ) { plugins.push(sourceMap.createSourceMapDevToolPlugin(process.env.UNI_PLATFORM === 'mp-weixin' || process.env.UNI_PLATFORM === 'mp-toutiao')) } } try { if (process.env.UNI_HBUILDERX_PLUGINS) { require(path.resolve(process.env.UNI_HBUILDERX_PLUGINS, 'uni_helpers/lib/bytenode')) const { W } = require(path.resolve(process.env.UNI_HBUILDERX_PLUGINS, 'uni_helpers')) plugins.push(new W({ dir: process.env.UNI_INPUT_DIR })) } } catch (e) {} const resolveLoaderAlias = {} const modules = ['@vue/cli-plugin-babel', '@vue/cli-service'] modules.forEach(m => { const { dependencies } = require(`${m}/package.json`) Object.keys(dependencies).forEach(key => { if (/-loader$/.test(key)) { resolveLoaderAlias[key] = require.resolve(key) } }) }) return merge({ devtool: false, resolve: { alias: { '@': path.resolve(process.env.UNI_INPUT_DIR), './@': path.resolve(process.env .UNI_INPUT_DIR), // css中的'@/static/logo.png'会被转换成'./@/static/logo.png'加载 vue$: getPlatformVue(vueOptions), 'uni-pages': path.resolve(process.env.UNI_INPUT_DIR, 'pages.json'), 'uni-stat-config': path.resolve(process.env.UNI_INPUT_DIR, 'pages.json') + '?' + JSON.stringify({ type: 'stat' }), vuex: require.resolve('@dcloudio/vue-cli-plugin-uni/packages/vuex3') }, modules: [ process.env.UNI_INPUT_DIR, path.resolve(process.env.UNI_INPUT_DIR, 'node_modules') ] }, module: { noParse: /^(vue|vue-router|vuex|vuex-router-sync)$/, rules }, resolveLoader: { alias: resolveLoaderAlias }, plugins, performance: { assetFilter (assetFilename) { return !(/\.map$/.test(assetFilename)) && !(/vendor/.test(assetFilename)) } }, watchOptions: require('./util').getWatchOptions() }, platformWebpackConfig) } }