1 | import { existsSync, readFileSync } from 'fs';
|
2 | import { join, resolve } from 'path';
|
3 | import assert from 'assert';
|
4 | import stripJsonComments from 'strip-json-comments';
|
5 | import didyoumean from 'didyoumean';
|
6 | import chalk from 'chalk';
|
7 | import isEqual from 'lodash.isequal';
|
8 | import isPlainObject from 'is-plain-object';
|
9 | import { clearConsole } from '../reactDevUtils';
|
10 | import { watch, unwatch } from './watch';
|
11 | import getPlugins from './getPlugins';
|
12 |
|
13 | const debug = require('debug')('af-webpack:getUserConfig');
|
14 |
|
15 | const plugins = getPlugins();
|
16 | const pluginNames = plugins.map(p => p.name);
|
17 | const pluginsMapByName = plugins.reduce((memo, p) => {
|
18 | memo[p.name] = p;
|
19 | return memo;
|
20 | }, {});
|
21 |
|
22 | let devServer = null;
|
23 | const USER_CONFIGS = 'USER_CONFIGS';
|
24 |
|
25 | function throwError(msg) {
|
26 | printError(msg);
|
27 | throw new Error(msg);
|
28 | }
|
29 |
|
30 | function printError(messages) {
|
31 | if (devServer) {
|
32 | devServer.sockWrite(
|
33 | devServer.sockets,
|
34 | 'errors',
|
35 | typeof messages === 'string' ? [messages] : messages,
|
36 | );
|
37 | }
|
38 | }
|
39 |
|
40 | function reload() {
|
41 | devServer.sockWrite(devServer.sockets, 'content-changed');
|
42 | }
|
43 |
|
44 | function restart(why) {
|
45 | clearConsole();
|
46 | console.log(chalk.green(`Since ${why}, try to restart the server`));
|
47 | unwatch();
|
48 | devServer.close();
|
49 | process.send({ type: 'RESTART' });
|
50 | }
|
51 |
|
52 | function merge(oldObj, newObj) {
|
53 | for (const key in newObj) {
|
54 | if (Array.isArray(newObj[key]) && Array.isArray(oldObj[key])) {
|
55 | oldObj[key] = oldObj[key].concat(newObj[key]);
|
56 | } else if (isPlainObject(newObj[key]) && isPlainObject(oldObj[key])) {
|
57 | oldObj[key] = Object.assign(oldObj[key], newObj[key]);
|
58 | } else {
|
59 | oldObj[key] = newObj[key];
|
60 | }
|
61 | }
|
62 | }
|
63 |
|
64 | function replaceNpmVariables(value, pkg) {
|
65 | if (typeof value === 'string') {
|
66 | return value
|
67 | .replace('$npm_package_name', pkg.name)
|
68 | .replace('$npm_package_version', pkg.version);
|
69 | } else {
|
70 | return value;
|
71 | }
|
72 | }
|
73 |
|
74 | export default function getUserConfig(opts = {}) {
|
75 | const {
|
76 | cwd = process.cwd(),
|
77 | configFile = '.webpackrc',
|
78 | disabledConfigs = [],
|
79 | preprocessor,
|
80 | } = opts;
|
81 |
|
82 |
|
83 |
|
84 |
|
85 | const rcFile = resolve(cwd, configFile);
|
86 | const jsRCFile = resolve(cwd, `${configFile}.js`);
|
87 |
|
88 | assert(
|
89 | !(existsSync(rcFile) && existsSync(jsRCFile)),
|
90 | `${configFile} file and ${configFile}.js file can not exist at the same time.`,
|
91 | );
|
92 |
|
93 | let config = {};
|
94 | if (existsSync(rcFile)) {
|
95 | config = JSON.parse(stripJsonComments(readFileSync(rcFile, 'utf-8')));
|
96 | }
|
97 | if (existsSync(jsRCFile)) {
|
98 |
|
99 | delete require.cache[jsRCFile];
|
100 | config = require(jsRCFile);
|
101 | if (config.default) {
|
102 | config = config.default;
|
103 | }
|
104 | }
|
105 | if (typeof preprocessor === 'function') {
|
106 | config = preprocessor(config);
|
107 | }
|
108 |
|
109 |
|
110 | const context = {
|
111 | cwd,
|
112 | };
|
113 |
|
114 |
|
115 | let errorMsg = null;
|
116 | Object.keys(config).forEach(key => {
|
117 |
|
118 | if (disabledConfigs.includes(key)) {
|
119 | errorMsg = `Configuration item ${key} is disabled, please remove it.`;
|
120 | }
|
121 |
|
122 | if (!pluginNames.includes(key)) {
|
123 | const guess = didyoumean(key, pluginNames);
|
124 | const affix = guess ? `do you meen ${guess} ?` : 'please remove it.';
|
125 | errorMsg = `Configuration item ${key} is not valid, ${affix}`;
|
126 | } else {
|
127 |
|
128 | const plugin = pluginsMapByName[key];
|
129 | if (plugin.validate) {
|
130 | try {
|
131 | plugin.validate.call(context, config[key]);
|
132 | } catch (e) {
|
133 | errorMsg = e.message;
|
134 | }
|
135 | }
|
136 | }
|
137 | });
|
138 |
|
139 |
|
140 | if (errorMsg) {
|
141 | if ( opts.setConfig) {
|
142 | opts.setConfig(config);
|
143 | }
|
144 | throwError(errorMsg);
|
145 | }
|
146 |
|
147 |
|
148 | if (config.env) {
|
149 | if (config.env[process.env.NODE_ENV]) {
|
150 | merge(config, config.env[process.env.NODE_ENV]);
|
151 | }
|
152 | delete config.env;
|
153 | }
|
154 |
|
155 |
|
156 | const pkgFile = resolve(cwd, 'package.json');
|
157 | if (Object.keys(config).length && existsSync(pkgFile)) {
|
158 | const pkg = JSON.parse(readFileSync(pkgFile, 'utf-8'));
|
159 | config = Object.keys(config).reduce((memo, key) => {
|
160 | memo[key] = replaceNpmVariables(config[key], pkg);
|
161 | return memo;
|
162 | }, {});
|
163 | }
|
164 |
|
165 | let configFailed = false;
|
166 | function watchConfigsAndRun(_devServer, watchOpts = {}) {
|
167 | devServer = _devServer;
|
168 |
|
169 | const watcher = watchConfigs(opts);
|
170 | if (watcher) {
|
171 | watcher.on('all', () => {
|
172 | try {
|
173 | if (watchOpts.beforeChange) {
|
174 | watchOpts.beforeChange();
|
175 | }
|
176 |
|
177 | const { config: newConfig } = getUserConfig({
|
178 | ...opts,
|
179 | setConfig(newConfig) {
|
180 | config = newConfig;
|
181 | },
|
182 | });
|
183 |
|
184 |
|
185 | if (configFailed) {
|
186 | configFailed = false;
|
187 | reload();
|
188 | }
|
189 |
|
190 |
|
191 | for (const plugin of plugins) {
|
192 | const { name, onChange } = plugin;
|
193 |
|
194 | if (!isEqual(newConfig[name], config[name])) {
|
195 | debug(
|
196 | `Config ${name} changed, from ${JSON.stringify(
|
197 | config[name],
|
198 | )} to ${JSON.stringify(newConfig[name])}`,
|
199 | );
|
200 | (onChange || restart.bind(null, `${name} changed`)).call(null, {
|
201 | name,
|
202 | val: config[name],
|
203 | newVal: newConfig[name],
|
204 | config,
|
205 | newConfig,
|
206 | });
|
207 | }
|
208 | }
|
209 | } catch (e) {
|
210 | configFailed = true;
|
211 | console.error(chalk.red(`Watch handler failed, since ${e.message}`));
|
212 | console.error(e);
|
213 | }
|
214 | });
|
215 | }
|
216 | }
|
217 |
|
218 | debug(`UserConfig: ${JSON.stringify(config)}`);
|
219 |
|
220 | return { config, watch: watchConfigsAndRun };
|
221 | }
|
222 |
|
223 | export function watchConfigs(opts = {}) {
|
224 | const { cwd = process.cwd(), configFile = '.webpackrc' } = opts;
|
225 |
|
226 | const rcFile = resolve(cwd, configFile);
|
227 | const jsRCFile = resolve(cwd, `${configFile}.js`);
|
228 |
|
229 | return watch(USER_CONFIGS, [rcFile, jsRCFile]);
|
230 | }
|
231 |
|
232 | export function unwatchConfigs() {
|
233 | unwatch(USER_CONFIGS);
|
234 | }
|