Skip to content

Commit ecf7b47

Browse files
committed
👽 update webpack config and react page demo.
1 parent b72042e commit ecf7b47

19 files changed

+379
-27
lines changed

.eslintrc.js

+1
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,6 @@ module.exports = {
2727
'semi': ['error', 'always'],
2828
'no-mixed-spaces-and-tabs': 'off',
2929
'@typescript-eslint/ban-ts-comment': 'off',
30+
'@typescript-eslint/explicit-module-boundary-types': 'off',
3031
}
3132
};

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
"autoprefixer": "^10.2.4",
5050
"babel-loader": "^8.2.2",
5151
"babel-plugin-import": "^1.13.3",
52+
"case-sensitive-paths-webpack-plugin": "^2.4.0",
5253
"chalk": "^4.1.0",
5354
"clean-webpack-plugin": "^3.0.0",
5455
"cross-env": "^7.0.3",
@@ -59,6 +60,7 @@
5960
"eslint-plugin-promise": "^4.3.1",
6061
"eslint-plugin-react": "^7.22.0",
6162
"eslint-plugin-standard": "^5.0.0",
63+
"find-up": "^5.0.0",
6264
"html-webpack-plugin": "^5.2.0",
6365
"less": "^4.1.1",
6466
"less-loader": "^8.0.0",

scripts/helper.js

+8-7
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
const fs = require('fs');
2-
const path = require('path');
32
const os = require('os');
43
const dotenv = require('dotenv');
4+
const paths = require('./paths');
55

6-
const NODE_ENV = process.env.NODE_ENV;
6+
const pkg = require(paths.appPackageJson);
77

8-
const rootPath = path.resolve(__dirname, '..');
8+
const NODE_ENV = process.env.NODE_ENV;
99

1010
function getProcessEnv() {
1111
const REACT_APP_REGEXP = /^REACT_APP_/i;
@@ -17,10 +17,10 @@ function getProcessEnv() {
1717
const result = {};
1818

1919
const dotenvFiles = [
20-
`${rootPath}/.env`,
21-
`${rootPath}/.env.local`,
22-
`${rootPath}/.env.${NODE_ENV}.local`,
23-
`${rootPath}/.env.${NODE_ENV}`,
20+
`${paths.appPath}/.env`,
21+
`${paths.appPath}/.env.local`,
22+
`${paths.appPath}/.env.${NODE_ENV}.local`,
23+
`${paths.appPath}/.env.${NODE_ENV}`,
2424
].filter(Boolean);
2525

2626
dotenvFiles.forEach(dotenvFile => {
@@ -35,6 +35,7 @@ function getProcessEnv() {
3535
});
3636

3737
result.NODE_ENV = NODE_ENV;
38+
result.packageName = pkg.name;
3839

3940
return result;
4041
}

scripts/paths.js

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
const path = require('path');
2+
const fs = require('fs');
3+
4+
// 根目录
5+
const appDirectory = fs.realpathSync(process.cwd());
6+
7+
// 依据根目录,找到相对文件或相对目录
8+
const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
9+
10+
const moduleFileExtensions = [
11+
'web.mjs',
12+
'mjs',
13+
'web.js',
14+
'js',
15+
'web.ts',
16+
'ts',
17+
'web.tsx',
18+
'tsx',
19+
'json',
20+
'web.jsx',
21+
'jsx',
22+
];
23+
24+
const resolveModule = (resolveFn, filePath) => {
25+
const extension = moduleFileExtensions.find(extension => {
26+
return fs.existsSync(resolveFn(`${filePath}.${extension}`))
27+
}
28+
);
29+
30+
if (extension) {
31+
return resolveFn(`${filePath}.${extension}`);
32+
}
33+
34+
return resolveFn(`${filePath}.js`);
35+
};
36+
37+
module.exports = {
38+
// 解析 env 环境变量
39+
dotenv: resolveApp('.env'),
40+
// 项目根目录
41+
appPath: resolveApp('.'),
42+
// 项目打包的目录
43+
appBuild: resolveApp('build'),
44+
// public 资源目录
45+
appPublic: resolveApp('public'),
46+
// public 目录下的 index.html 文件
47+
appHtml: resolveApp('public/index.html'),
48+
// 解析入口文件,入口文件可能是 index.js, index.jsx, index.ts, index.tsx
49+
appIndexJs: resolveModule(resolveApp, 'src/index'),
50+
// package.json 的路径
51+
appPackageJson: resolveApp('package.json'),
52+
// src 目录
53+
appSrc: resolveApp('src'),
54+
// tsconfig 的路径
55+
appTsConfig: resolveApp('tsconfig.json'),
56+
// jsconfig 的路径
57+
appJsConfig: resolveApp('jsconfig.json'),
58+
// yarn.lock 文件的路径
59+
yarnLockFile: resolveApp('yarn.lock'),
60+
// setupTests 文件的路径
61+
testsSetup: resolveModule(resolveApp, 'src/setupTests'),
62+
// setupProxy 文件的路径
63+
proxySetup: resolveApp('src/setupProxy.js'),
64+
// node_modules 的目录路径
65+
appNodeModules: resolveApp('node_modules'),
66+
// service-worker 文件的路径
67+
swSrc: resolveModule(resolveApp, 'src/service-worker'),
68+
};
+146
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
/**
2+
* Copyright (c) 2015-present, Facebook, Inc.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
'use strict';
9+
10+
const chalk = require('chalk');
11+
const findUp = require('find-up');
12+
const path = require('path');
13+
14+
class ModuleNotFoundPlugin {
15+
constructor(appPath, yarnLockFile) {
16+
this.appPath = appPath;
17+
this.yarnLockFile = yarnLockFile;
18+
19+
this.useYarnCommand = this.useYarnCommand.bind(this);
20+
this.getRelativePath = this.getRelativePath.bind(this);
21+
this.prettierError = this.prettierError.bind(this);
22+
}
23+
24+
useYarnCommand() {
25+
try {
26+
return findUp.sync('yarn.lock', { cwd: this.appPath }) != null;
27+
} catch (_) {
28+
return false;
29+
}
30+
}
31+
32+
getRelativePath(_file) {
33+
let file = path.relative(this.appPath, _file);
34+
if (file.startsWith('..')) {
35+
file = _file;
36+
} else if (!file.startsWith('.')) {
37+
file = '.' + path.sep + file;
38+
}
39+
return file;
40+
}
41+
42+
prettierError(err) {
43+
let { details: _details = '', origin } = err;
44+
45+
if (origin == null) {
46+
const caseSensitivity =
47+
err.message &&
48+
/\[CaseSensitivePathsPlugin\] `(.*?)` .* `(.*?)`/.exec(err.message);
49+
if (caseSensitivity) {
50+
const [, incorrectPath, actualName] = caseSensitivity;
51+
const actualFile = this.getRelativePath(
52+
path.join(path.dirname(incorrectPath), actualName)
53+
);
54+
const incorrectName = path.basename(incorrectPath);
55+
err.message = `Cannot find file: '${incorrectName}' does not match the corresponding name on disk: '${actualFile}'.`;
56+
}
57+
return err;
58+
}
59+
60+
const file = this.getRelativePath(origin.resource);
61+
let details = _details.split('\n');
62+
63+
const request = /resolve '(.*?)' in '(.*?)'/.exec(details);
64+
if (request) {
65+
const isModule = details[1] && details[1].includes('module');
66+
const isFile = details[1] && details[1].includes('file');
67+
68+
let [, target, context] = request;
69+
context = this.getRelativePath(context);
70+
if (isModule) {
71+
const isYarn = this.useYarnCommand();
72+
details = [
73+
`Cannot find module: '${target}'. Make sure this package is installed.`,
74+
'',
75+
'You can install this package by running: ' +
76+
(isYarn
77+
? chalk.bold(`yarn add ${target}`)
78+
: chalk.bold(`npm install ${target}`)) +
79+
'.',
80+
];
81+
} else if (isFile) {
82+
details = [`Cannot find file '${target}' in '${context}'.`];
83+
} else {
84+
details = [err.message];
85+
}
86+
} else {
87+
details = [err.message];
88+
}
89+
err.message = [file, ...details].join('\n').replace('Error: ', '');
90+
91+
const isModuleScopePluginError =
92+
err.error && err.error.__module_scope_plugin;
93+
if (isModuleScopePluginError) {
94+
err.message = err.message.replace('Module not found: ', '');
95+
}
96+
return err;
97+
}
98+
99+
apply(compiler) {
100+
const { prettierError } = this;
101+
compiler.hooks.make.intercept({
102+
register(tap) {
103+
if (
104+
!(tap.name === 'MultiEntryPlugin' || tap.name === 'SingleEntryPlugin')
105+
) {
106+
return tap;
107+
}
108+
return Object.assign({}, tap, {
109+
fn: (compilation, callback) => {
110+
tap.fn(compilation, (err, ...args) => {
111+
if (err && err.name === 'ModuleNotFoundError') {
112+
err = prettierError(err);
113+
}
114+
callback(err, ...args);
115+
});
116+
},
117+
});
118+
},
119+
});
120+
compiler.hooks.normalModuleFactory.tap('ModuleNotFoundPlugin', nmf => {
121+
nmf.hooks.afterResolve.intercept({
122+
register(tap) {
123+
if (tap.name !== 'CaseSensitivePathsPlugin') {
124+
return tap;
125+
}
126+
return Object.assign({}, tap, {
127+
fn: (compilation, callback) => {
128+
tap.fn(compilation, (err, ...args) => {
129+
if (
130+
err &&
131+
err.message &&
132+
err.message.includes('CaseSensitivePathsPlugin')
133+
) {
134+
err = prettierError(err);
135+
}
136+
callback(err, ...args);
137+
});
138+
},
139+
});
140+
},
141+
});
142+
});
143+
}
144+
}
145+
146+
module.exports = ModuleNotFoundPlugin;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/**
2+
* Copyright (c) 2015-present, Facebook, Inc.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
// This webpack plugin ensures `npm install <library>` forces a project rebuild.
9+
// We’re not sure why this isn't webpack's default behavior.
10+
// See https://github.com/facebook/create-react-app/issues/186.
11+
12+
'use strict';
13+
14+
class WatchMissingNodeModulesPlugin {
15+
constructor(nodeModulesPath) {
16+
this.nodeModulesPath = nodeModulesPath;
17+
}
18+
19+
apply(compiler) {
20+
compiler.hooks.emit.tap('WatchMissingNodeModulesPlugin', compilation => {
21+
var missingDeps = Array.from(compilation.missingDependencies);
22+
var nodeModulesPath = this.nodeModulesPath;
23+
24+
// If any missing files are expected to appear in node_modules...
25+
if (missingDeps.some(file => file.includes(nodeModulesPath))) {
26+
// ...tell webpack to watch node_modules recursively until they appear.
27+
compilation.contextDependencies.add(nodeModulesPath);
28+
}
29+
});
30+
}
31+
}
32+
33+
module.exports = WatchMissingNodeModulesPlugin;

scripts/webpack.config.dev.js

+10-6
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const chalk = require('chalk');
77
const baseConfig = require('./webpack.config');
88
const pkg = require('../package.json');
99
const helper = require('./helper');
10+
const paths = require('./paths');
1011

1112
const defaultConfig = {
1213
port: process.env.REACT_APP_PORT || 18000
@@ -18,13 +19,15 @@ module.exports = merge(baseConfig, {
1819
devtool: 'source-map',
1920

2021
devServer: {
21-
port: defaultConfig.port,
22-
hot: true,
22+
disableHostCheck: true,
2323
compress: true,
24-
clientLogLevel: 'silent',
25-
contentBase: path.resolve(__dirname, '../public'),
24+
contentBase: paths.appPublic,
25+
watchContentBase: true,
26+
hot: true,
27+
transportMode: 'ws',
28+
injectClient: false,
29+
port: defaultConfig.port,
2630
historyApiFallback: {
27-
index: path.resolve(__dirname, '../public/index.html'),
2831
disableDotRule: true,
2932
},
3033
after(app, server) {
@@ -68,7 +71,8 @@ module.exports = merge(baseConfig, {
6871
new HtmlWebpackPlugin({
6972
template: path.resolve(__dirname, '../public/index.html'),
7073
title: pkg.name,
71-
})
74+
}),
75+
7276
]
7377

7478
});

scripts/webpack.config.js

+14-4
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
const path = require('path');
22
const webpack = require('webpack');
3-
3+
// 大小写敏感
4+
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
5+
// 模块没找到
6+
const ModuleNotFoundPlugin = require('./plugins/ModuleNotFoundPlugin');
7+
// 监控模块缺失
8+
const WatchMissingNodeModulesPlugin = require('./plugins/WatchMissingNodeModulesPlugin');
49
const helper = require('./helper');
10+
const paths = require('./paths');
511

612
const envObj = helper.getProcessEnv();
713

814
module.exports = {
9-
entry: path.resolve(__dirname, '../src/index.tsx'),
15+
entry: paths.appIndexJs,
1016

1117
module: {
1218
rules: [
@@ -25,15 +31,19 @@ module.exports = {
2531

2632
resolve: {
2733
alias: {
28-
'@': path.resolve(__dirname, '../src'),
29-
'~': path.resolve(__dirname, '../node_modules'),
34+
'@': paths.appSrc,
35+
'~': paths.appNodeModules,
3036
},
3137
extensions: ['.ts', '.tsx', '.js', '.jsx', '.json']
3238
},
3339

3440
plugins: [
3541
new webpack.DefinePlugin({'process.env': JSON.stringify(envObj)}),
3642
new webpack.EnvironmentPlugin(envObj),
43+
new CaseSensitivePathsPlugin(),
44+
new ModuleNotFoundPlugin(paths.appPath),
45+
new webpack.HotModuleReplacementPlugin(),
46+
new WatchMissingNodeModulesPlugin(paths.appNodeModules),
3747
],
3848

3949
stats: 'minimal',

0 commit comments

Comments
 (0)