UNPKG

6.16 kBJavaScriptView Raw
1const path = require('path')
2const os = require('os')
3const chalk = require('chalk')
4
5const isLocalPath = v => /^[./]|(^[a-zA-Z]:)/.test(v)
6
7const normalizeEntry = v => {
8 if (v.startsWith('module:')) {
9 return v.replace(/^module:/, '')
10 }
11 if (isLocalPath(v)) {
12 return v
13 }
14 return `./${v}`
15}
16
17module.exports = (config, api) => {
18 /** Set entry */
19 let { entry } = api.config
20 if (typeof entry === 'string') {
21 entry = {
22 index: [entry]
23 }
24 } else if (Array.isArray(entry)) {
25 entry = {
26 index: entry
27 }
28 } else {
29 entry = Object.assign({}, entry)
30 }
31
32 for (const name of Object.keys(entry)) {
33 entry[name] = entry[name].map(v => normalizeEntry(v))
34 }
35
36 config.merge({ entry })
37
38 /** Set extensions */
39 config.resolve.extensions.merge(['.js', '.json', '.jsx', '.ts', '.tsx'])
40
41 /** Support react-native-web by default, cuz why not? */
42 // https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
43 config.resolve.alias.set('react-native', 'react-native-web')
44
45 /** Set mode */
46 config.mode(api.mode === 'production' ? 'production' : 'development')
47
48 config.merge({
49 // Disable webpack's default minimizer
50 // Minimization will be handled by mode:production plugin
51 optimization: {
52 minimize: false
53 },
54 // Disable default performance hints
55 // TODO: maybe add our custom one
56 performance: {
57 hints: false
58 }
59 })
60
61 /** Set output */
62 config.output.path(api.resolveOutDir())
63 config.output.filename(api.webpackUtils.fileNames.js)
64 config.output.publicPath(api.config.output.publicUrl)
65
66 /** Set format */
67 const { format, moduleName } = api.config.output
68 if (format === 'cjs') {
69 config.output.libraryTarget('commonjs2')
70 } else if (format === 'umd') {
71 if (!moduleName) {
72 api.logger.error(
73 `"moduleName" is missing for ${chalk.bold('umd')} format`
74 )
75 api.logger.tip(
76 `To add it, simply use flag ${chalk.cyan('--module-name <name>')}`
77 )
78 api.logger.tip(
79 `Like ${chalk.cyan(
80 '--module-name React'
81 )} if you're building the React.js source code`
82 )
83 throw new api.PoiError({
84 message: 'missing moduleName for umd format',
85 dismiss: true
86 })
87 }
88 config.output.libraryTarget('umd')
89 config.output.library(moduleName)
90 }
91
92 /** Resolve loaders */
93 config.resolveLoader.modules
94 .add('node_modules')
95 .add(path.dirname(path.dirname(require.resolve('babel-loader/package'))))
96
97 /** Resolve modules */
98 config.resolve.modules
99 .add('node_modules')
100 .add(path.dirname(path.dirname(require.resolve('babel-loader/package'))))
101
102 // Add progress bar
103 if (
104 api.cli.options.progress !== false &&
105 process.stdout.isTTY &&
106 !process.env.CI &&
107 api.mode !== 'test'
108 ) {
109 const homeRe = new RegExp(os.homedir(), 'g')
110 config.plugin('progress').use(require('webpack').ProgressPlugin, [
111 /**
112 * @param {Number} per
113 * @param {string} message
114 * @param {string[]} args
115 */
116 (per, message, ...args) => {
117 const spinner = require('../utils/spinner')
118
119 const msg = `${(per * 100).toFixed(2)}% ${message} ${args
120 .map(arg => {
121 return arg.replace(homeRe, '~')
122 })
123 .join(' ')}`
124
125 if (per === 0) {
126 spinner.start(msg)
127 } else if (per === 1) {
128 spinner.stop()
129 } else {
130 spinner.text = msg
131 }
132 }
133 ])
134 }
135
136 /** Add a default status reporter */
137 if (api.mode !== 'test') {
138 config.plugin('print-status').use(require('./PrintStatusPlugin'), [
139 {
140 printFileStats: true
141 }
142 ])
143 }
144
145 /** Add constants plugin */
146 config.plugin('constants').use(require('webpack').DefinePlugin, [
147 Object.assign({}, api.config.constants, {
148 __PUBLIC_URL__: JSON.stringify(api.config.output.publicUrl)
149 })
150 ])
151
152 /** Inject envs */
153 config
154 .plugin('envs')
155 .use(require('webpack').DefinePlugin, [api.webpackUtils.envs])
156
157 /** Miniize JS files */
158 if (api.config.output.minimize) {
159 config.plugin('minimize').use(require('terser-webpack-plugin'), [
160 {
161 cache: true,
162 parallel: true,
163 sourceMap: api.config.output.sourceMap,
164 terserOptions: {
165 parse: {
166 // we want terser to parse ecma 8 code. However, we don't want it
167 // to apply any minfication steps that turns valid ecma 5 code
168 // into invalid ecma 5 code. This is why the 'compress' and 'output'
169 // sections only apply transformations that are ecma 5 safe
170 // https://github.com/facebook/create-react-app/pull/4234
171 ecma: 8
172 },
173 compress: {
174 ecma: 5,
175 warnings: false,
176 // Disabled because of an issue with Uglify breaking seemingly valid code:
177 // https://github.com/facebook/create-react-app/issues/2376
178 // Pending further investigation:
179 // https://github.com/mishoo/UglifyJS2/issues/2011
180 comparisons: false,
181 // Disabled because of an issue with Terser breaking valid code:
182 // https://github.com/facebook/create-react-app/issues/5250
183 // Pending futher investigation:
184 // https://github.com/terser-js/terser/issues/120
185 inline: 2
186 },
187 mangle: {
188 safari10: true
189 },
190 output: {
191 ecma: 5,
192 comments: false,
193 // Turned on because emoji and regex is not minified properly using default
194 // https://github.com/facebook/create-react-app/issues/2488
195 ascii_only: true
196 }
197 }
198 }
199 ])
200 }
201
202 if (!api.isProd) {
203 config
204 .plugin('case-sensitive-paths')
205 .use(require('case-sensitive-paths-webpack-plugin'))
206
207 const nodeModulesDir =
208 api.configLoader.resolve('node_modules') || api.resolveCwd('node_modules')
209 config
210 .plugin('watch-missing-node-modules')
211 .use(require('@poi/dev-utils/WatchMissingNodeModulesPlugin'), [
212 nodeModulesDir
213 ])
214 }
215}