UNPKG

11.9 kBJavaScriptView Raw
1'use strict'
2
3const webpack = require('webpack')
4// PnpWebpackPlugin 即插即用,要使用 require.resolve 解析 loader 路径
5const PnpWebpackPlugin = require('pnp-webpack-plugin')
6const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin')
7const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin')
8const tsFormatter = require('react-dev-utils/typescriptFormatter')
9const ModuleNotFoundPlugin = require('react-dev-utils/ModuleNotFoundPlugin')
10const { resolve } = require('@mara/devkit')
11const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent')
12const VueLoaderPlugin = require('vue-loader/lib/plugin')
13
14const getStyleLoaders = require('./loaders/style-loader')
15const { SinaHybridPlugin, getCommonPkgConf } = require('../lib/hybrid')
16const config = require('../config')
17const { GLOB, VIEWS_DIR, TARGET } = require('../config/const')
18const { babelLoader } = require('./loaders/babel-loader')
19const {
20 vueLoaderOptions,
21 vueLoaderCacheConfig
22} = require('./loaders/vue-loader.conf')
23const { getEntries, isInstalled } = require('../lib/utils')
24const paths = config.paths
25
26module.exports = function(
27 { entry, buildEnv, target, publicPath, version },
28 cmd
29) {
30 const isDev = process.env.NODE_ENV === 'development'
31 const isProd = process.env.NODE_ENV === 'production'
32 const isLib = cmd === 'lib'
33 const isDevOrBuildCmd = cmd === 'dev' || cmd === 'build'
34 const isHybridMode = target === TARGET.APP
35 const assetsDir = isLib ? '' : 'static/'
36 const entryGlob = `${VIEWS_DIR}/${entry}/${GLOB.MAIN_ENTRY}`
37 const useTypeScript = config.useTypeScript
38 const { vueRuntimeOnly } = config.compiler
39 const tsCompilerOptions = {
40 // 输出 ESM 模块系统代码,交由 babel 二次编译
41 module: 'esnext',
42 // 输出最新语法,交由 babel 二次编译
43 target: 'esnext',
44 moduleResolution: 'node',
45 resolveJsonModule: true,
46 noEmit: true,
47 // https://www.tslang.cn/docs/handbook/jsx.html
48 // 保留 jsx 语法格式,交由 babel 做后续处理
49 jsx: 'preserve'
50 }
51
52 // react native web 为 .web. 后缀
53 const extensions = [
54 '.web.mjs',
55 '.mjs',
56 '.web.js',
57 '.js',
58 '.web.ts',
59 '.ts',
60 '.web.tsx',
61 '.tsx',
62 '.web.jsx',
63 '.jsx',
64 '.vue',
65 '.json'
66 ]
67
68 // 统一设置 loaders 配置
69 getStyleLoaders.publicPath = publicPath
70 getStyleLoaders.isLib = isLib
71
72 let externals = []
73 let entryConf = {}
74 let commonPkgPath = ''
75
76 const useCommonPkg =
77 isDevOrBuildCmd &&
78 isHybridMode &&
79 config.compiler.splitSNC &&
80 isInstalled('@mfelibs/hybridcontainer')
81
82 // hybrid SDK 提升,以尽快建立 jsbridge
83 if (useCommonPkg) {
84 const sncConf = getCommonPkgConf(entryGlob)
85
86 // 使用拆分后的 entry 配置
87 entryConf = sncConf.entry
88 commonPkgPath = sncConf.commonPkgPath
89 externals.push(...sncConf.externals)
90 } else {
91 entryConf = getEntries(entryGlob, require.resolve('../lib/polyfills'))
92 }
93
94 const baseConfig = {
95 // dev, build 环境依赖 base.entry,务必提供
96 entry: entryConf,
97 output: {
98 path: paths.dist,
99 filename: 'static/js/[name].js',
100 chunkFilename: 'static/js/[name].chunk.js',
101 // TODO: remove this when upgrading to webpack 5
102 futureEmitAssets: true
103 },
104 resolve: {
105 // disable symlinks
106 symlinks: false,
107 // js first
108 extensions: extensions.filter(
109 ext => useTypeScript || !ext.includes('ts')
110 ),
111 // https://doc.webpack-china.org/configuration/resolve/#resolve-mainfields
112 // source 为自定义拓展属性,表示源码入口
113 mainFields: ['source', 'browser', 'module', 'main'],
114 modules: ['node_modules', paths.nodeModules],
115 alias: {
116 // 使用 `~` 作为 src 别名
117 // 使用特殊符号防止与 npm 包冲突
118 // import '~/css/style.css'
119 '~': paths.src,
120 // 末尾指定 $ 防止误匹配
121 'react-native$': 'react-native-web',
122 vue$: `vue/dist/vue${vueRuntimeOnly ? '.runtime' : ''}.esm.js`
123 },
124 plugins: [
125 // Adds support for installing with Plug'n'Play, leading to faster installs and adding
126 // guards against forgotten dependencies and such.
127 PnpWebpackPlugin,
128 // Prevents users from importing files from outside of src/ (or node_modules/).
129 // This often causes confusion because we only process files within src/ with babel.
130 // To fix this, we prevent you from importing files out of src/ -- if you'd like to,
131 // please link the files into your node_modules/ and let module-resolution kick in.
132 // Make sure your source files are compiled, as they will not be processed in any way.
133 new ModuleScopePlugin(paths.src, [paths.packageJson])
134 ]
135 },
136 resolveLoader: {
137 plugins: [
138 // Also related to Plug'n'Play, but this time it tells Webpack to load its loaders
139 // from the current package.
140 PnpWebpackPlugin.moduleLoader(module)
141 ]
142 },
143 module: {
144 // ts 模式输出的 interface 不被识别,
145 // 这里不使用严格模式,使构建继续下去
146 // https://github.com/webpack/webpack/issues/7378
147 strictExportPresence: false,
148 noParse: /^(vue|vue-router|vuex|vuex-router-sync)$/,
149 rules: [
150 // Disable require.ensure as it's not a standard language feature.
151 // 为了兼容 bundle-loader 暂时不启用
152 // { parser: { requireEnsure: false } },
153 {
154 test: /\.css$/,
155 exclude: /\.module\.css$/,
156 oneOf: getStyleLoaders()
157 },
158 {
159 test: /\.module\.css$/,
160 oneOf: getStyleLoaders({
161 modules: true,
162 getLocalIdent: getCSSModuleLocalIdent
163 })
164 },
165 {
166 test: /\.less$/,
167 oneOf: getStyleLoaders('less-loader')
168 },
169 {
170 test: /\.(scss|sass)$/,
171 oneOf: getStyleLoaders('sass-loader')
172 },
173 {
174 test: /\.styl(us)?$/,
175 oneOf: getStyleLoaders(
176 {
177 preferPathResolver: 'webpack'
178 },
179 'stylus-loader'
180 )
181 },
182 {
183 test: /\.ejs$/,
184 loader: require.resolve('marauder-ejs-loader')
185 },
186 {
187 test: /\.art$/,
188 loader: 'art-template-loader'
189 },
190 {
191 test: /\.vue$/,
192 // loader 总是从右到左地被调用
193 use: [
194 {
195 loader: require.resolve('cache-loader'),
196 options: vueLoaderCacheConfig
197 },
198 {
199 loader: require.resolve('vue-loader'),
200 options: vueLoaderOptions
201 }
202 ]
203 },
204 {
205 // Process JS with Babel.
206 oneOf: babelLoader(isProd, useTypeScript)
207 },
208 {
209 test: /\.(bmp|png|jpe?g|gif|webp)(\?.*)?$/,
210 loader: require.resolve('url-loader'),
211 options: {
212 limit: 1024 * 4,
213 tinifyKeys: config.tinifyKeys,
214 minify: isProd,
215 fallback: require.resolve('@mara/image-loader'),
216 name: `${assetsDir}img/[name].[contenthash:8].[ext]`
217 }
218 },
219 // don't base64-inline SVGs.
220 // https://github.com/facebookincubator/create-react-app/pull/1180
221 {
222 test: /\.(svg)(\?.*)?$/,
223 loader: require.resolve('@mara/image-loader'),
224 options: {
225 minify: isProd,
226 name: `${assetsDir}img/[name].[contenthash:8].[ext]`
227 }
228 },
229 {
230 test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
231 loader: require.resolve('file-loader'),
232 options: {
233 // 不支持 contenthash
234 name: `${assetsDir}fonts/[name].[hash:8].[ext]`
235 }
236 },
237 {
238 test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
239 loader: require.resolve('file-loader'),
240 options: {
241 name: `${assetsDir}media/[name].[contenthash:8].[ext]`
242 }
243 },
244 {
245 test: /\.txt$/,
246 loader: 'raw-loader'
247 }
248 ]
249 },
250 plugins: [
251 // This gives some necessary context to module not found errors, such as
252 // the requesting resource.
253 new ModuleNotFoundPlugin(paths.app),
254 // Moment.js is an extremely popular library that bundles large locale files
255 // by default due to how Webpack interprets its code. This is a practical
256 // solution that requires the user to opt into importing specific locales.
257 // https://github.com/jmblog/how-to-optimize-momentjs-with-webpack
258 // You can remove this if you don't use Moment.js:
259 new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
260 !isLib && new webpack.DefinePlugin(buildEnv.stringified),
261 new VueLoaderPlugin(),
262 // TypeScript type checking
263 useTypeScript &&
264 new ForkTsCheckerWebpackPlugin({
265 typescript: resolve.sync('typescript', {
266 basedir: paths.nodeModules
267 }),
268 vue: true,
269 async: isDev,
270 // 在 vue 模式下存在 bug,这里保持默认行为(vue 下禁用)
271 // https://github.com/Realytics/fork-ts-checker-webpack-plugin/issues/219
272 // useTypescriptIncrementalApi: true,
273 checkSyntacticErrors: true,
274 tsconfig: paths.tsConfig,
275 compilerOptions: tsCompilerOptions,
276 reportFiles: [
277 // 检查范围缩小到 src,屏蔽第三方模块的错误
278 'src/**',
279 '!**/__tests__/**',
280 '!**/?(*.)(spec|test).*',
281 '!**/src/setupProxy.*',
282 '!**/src/setupTests.*'
283 ],
284 watch: paths.src,
285 silent: true,
286 formatter: isProd ? tsFormatter : undefined
287 })
288 ].filter(Boolean),
289 // Some libraries import Node modules but don't use them in the browser.
290 // Tell Webpack to provide empty mocks for them so importing them works.
291 node: {
292 // prevent webpack from injecting useless setImmediate polyfill because Vue
293 // source contains it (although only uses it if it's native).
294 setImmediate: false,
295 module: 'empty',
296 dgram: 'empty',
297 dns: 'mock',
298 fs: 'empty',
299 http2: 'empty',
300 net: 'empty',
301 tls: 'empty',
302 child_process: 'empty'
303 },
304 // 禁用包体积性能警告
305 performance: {
306 hints: false
307 },
308 externals: externals
309 }
310
311 if (isDevOrBuildCmd) {
312 // Automatically split vendor and commons
313 // https://twitter.com/wSokra/status/969633336732905474
314 // https://medium.com/webpack/webpack-4-code-splitting-chunk-graph-and-the-splitchunks-optimization-be739a861366
315 baseConfig.optimization = {
316 splitChunks: {
317 chunks(chunk) {
318 const isServantOrSNC = /\.servant|__UNI_SNC__/.test(chunk.name)
319
320 // 仅输出 async 包
321 // hybrid prod 模式不拆 chunk,减少 IO 损耗
322 if (isServantOrSNC || (isHybridMode && isProd)) return false
323
324 return !!config.compiler.splitChunks
325 },
326 // 一些 CDN 不支持 `~`,因此指定为 `-``
327 automaticNameDelimiter: '_',
328 name: true
329 }
330 }
331
332 if (isHybridMode) {
333 // 确保在 copy Files 之前
334 baseConfig.plugins.push(
335 new SinaHybridPlugin(require('html-webpack-plugin'), {
336 entry: entry,
337 version: version,
338 publicPath: publicPath,
339 useCommonPkg: useCommonPkg,
340 commonPkgPath: commonPkgPath
341 })
342 )
343 }
344 }
345
346 if (!isLib && isInstalled('@mara/plugin-extract-comp-meta')) {
347 const VueMetaPlugin = require('@mara/plugin-extract-comp-meta/lib/vue-meta-plugin')
348
349 baseConfig.plugins.push(new VueMetaPlugin({ entry }))
350 }
351
352 return baseConfig
353}