1. 创建一个 react 的项目
首先,我们使用 npx create-react-app study-webpack-from-cra 创建一个 react 的项目;
然后,我们进入项目:cd study-webpack-from-cra;
使用 npm run eject 的命令,使 webpack 相关的配置完全暴露出来;
执行完成后,我们会得到如下的部分目录结构:
// 此处仅列举了部分的文件夹和文件
|--config
|-- env.js
|-- paths.js
|-- webpack.config.js
|-- webpackDevServer.config.js
|-- scripts
|-- build.js
|-- start.js
2. 解析 webpack 配置
npm run build
我们先来看看我们比较常用的打包命令 npm run build,它对应到执行 scripts 文件夹下的 build 文件,我们将该文件贴出主要代码,并逐行注释讲解:
'use strict';
// 设置是进行生产模式处理,在 webpack.config.js 文件里会大量用到。
process.env.BABEL_ENV = 'production';
process.env.NODE_ENV = 'production';
// 确保环境被读到。
// 这里值得说明的是,CommonJS 模块是运行时加载。当不同的文件共用process.env.NODE_ENV时,加载的时序就变得很重要。
// 如果将process.env.NODE_ENV = 'production' 的设置放在这句话之后,那么就会在env.js 抛出一个 NODE_ENV 不存在的错误。
require('../config/env');
// require(...)
// require(...)
// require(...)
// 一些关于文件大小的测试、Yarn使用、测试 Node.js 是否在终端上下文中运行等
const measureFileSizesBeforeBuild =
FileSizeReporter.measureFileSizesBeforeBuild;
const printFileSizesAfterBuild = FileSizeReporter.printFileSizesAfterBuild;
const useYarn = fs.existsSync(paths.yarnLockFile);
const WARN_AFTER_BUNDLE_GZIP_SIZE = 512 * 1024;
const WARN_AFTER_CHUNK_GZIP_SIZE = 1024 * 1024;
const isInteractive = process.stdout.isTTY;
// Warn and crash if required files are missing
if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
process.exit(1);
}
// 是否需要生成 stat json
const argv = process.argv.slice(2);
const writeStatsJson = argv.indexOf('--stats') !== -1;
// 从这里,正文开始了。
// 1. 拿到 webpack 在 production 模式下的配置。
const config = configFactory('production');
const { checkBrowsers } = require('react-dev-utils/browsersHelper');
checkBrowsers(paths.appPath, isInteractive)
.then(() => {
return measureFileSizesBeforeBuild(paths.appBuild);
})
.then(previousFileSizes => {
// 2. 检查浏览器、文件大小,并执行 build
return build(previousFileSizes);
})
function build(previousFileSizes) {
console.log('Creating an optimized production build...');
// 3. 进行打包
const compiler = webpack(config);
return new Promise((resolve, reject) => {
compiler.run((err, stats) => {
// ………………
return resolve(resolveArgs);
});
});
}
这里我删减了很多代码,并不是这些代码不重要,而是我们先把流程理解下,其中的部分代码,我们后面会详细讲解。
这里面主要就是三步:
- 拿到
webpack在production模式下的配置:const config = configFactory('production'); - 检查浏览器相关信息,并执行
build()函数:checkBrowsers(paths.appPath, isInteractive); - 在
build函数中执行webapck:const compiler = webpack(config);
我们通过断点,也能看到 config 中的 webpack 配置:
稍后在第三节中,我们会根据在 package.json 中传入不同的参数,来实现不同的打包方案。
npm run start
npm run start 命令的执行和 npm run build类似,只是要加入对应的 dev-server 的相关配置。
主要的区别也是在start.js文件中的checkBrowsers()函数下:
checkBrowsers(paths.appPath, isInteractive)
.then(() => {
// 给出端口占用的判断,也就是我们在启动时经常看到的端口占用是的提示
// 该函数在react-dev-utils/WebpackDevServerUtils中定义
return choosePort(HOST, DEFAULT_PORT);
})
.then(port => {
// 获取配置
const config = configFactory('development');
// 获取协议
const protocol = process.env.HTTPS === 'true' ? 'https' : 'http';
const appName = require(paths.appPackageJson).name;
const useTypeScript = fs.existsSync(paths.appTsConfig);
const tscCompileOnError = process.env.TSC_COMPILE_ON_ERROR === 'true';
const urls = prepareUrls(
protocol,
HOST,
port,
paths.publicUrlOrPath.slice(0, -1)
);
const devSocket = {
warnings: warnings =>
devServer.sockWrite(devServer.sockets, 'warnings', warnings),
errors: errors =>
devServer.sockWrite(devServer.sockets, 'errors', errors),
};
// 创建一个 webpack 的 compiler
// 这里主要是为了在 react-dev-utils/WebpackDevServerUtils.js 文件中定义一些钩子,
const compiler = createCompiler({
appName,
config,
devSocket,
urls,
useYarn,
useTypeScript,
tscCompileOnError,
webpack,
});
// 一些配置
const proxySetting = require(paths.appPackageJson).proxy;
const proxyConfig = prepareProxy(
proxySetting,
paths.appPublic,
paths.publicUrlOrPath
);
const serverConfig = createDevServerConfig(
proxyConfig,
urls.lanUrlForConfig
);
// 执行。
const devServer = new WebpackDevServer(compiler, serverConfig);
// Launch WebpackDevServer.
devServer.listen(port, HOST, err => {
if (err) {
return console.log(err);
}
if (isInteractive) {
clearConsole();
}
if (env.raw.FAST_REFRESH && semver.lt(react.version, '16.10.0')) {
console.log(
chalk.yellow(
`Fast Refresh requires React 16.10 or higher. You are using React ${react.version}.`
)
);
}
console.log(chalk.cyan('Starting the development server...\n'));
openBrowser(urls.localUrlForBrowser);
});
['SIGINT', 'SIGTERM'].forEach(function (sig) {
process.on(sig, function () {
devServer.close();
process.exit();
});
});
if (process.env.CI !== 'true') {
// Gracefully exit when stdin ends
process.stdin.on('end', function () {
devServer.close();
process.exit();
});
}
})
.catch(err => {
if (err && err.message) {
console.log(err.message);
}
process.exit(1);
});
这里,我们通过命令行的方式,执行了 webpack-dev-server的服务。
主要的逻辑在于这几行:
var compiler = webpack(config);
const devServer = new WebpackDevServer(compiler, serverConfig);
devServer.listen(port, () => {
})