本质上,有两种构建JavaScript应用程序的模式:开发和生产。以前,您已使用开发模式在本地开发环境中开始使用Webpack Dev Server。您可以更改源代码,Webpack再次将其捆绑,Webpack Dev Server会在浏览器中向您显示最新的开发版本。
但是,最终您希望拥有在Web服务器上的生产环境中部署Web应用程序所需的所有构建文件。由于Webpack将所有JavaScript源代码捆绑到一个 dist / index.html 文件中链接的 bundle.js 文件中,因此从 本质上讲 ,您只需要Web服务器上的这两个文件即可向任何人显示Web应用程序。让我们看看如何为您创建两个文件。
首先,您已经有了 dist / index.html 文件。如果打开它,您已经看到它使用了 webpack 创建的bundle.js文件,该文件是 src / 文件夹中所有JavaScript源代码文件中的文件。
<!DOCTYPE html>
<html>
<head>
<title>Hello Webpack bundled JavaScript Project</title>
</head>
<body>
<div>
<h1>Hello Webpack bundled JavaScript Project</h1>
</div>
<script src="./bundle.js"></script>
</body>
</html>
其次,如果键入npm start
,则Webpack将动态创建此 bundle.js 文件,该文件将用于Webpack Dev Server在开发模式下启动您的应用程序。您从未真正看到过 bundle.js 文件。
{
...
"scripts": {
"start": "webpack serve --config ./webpack.config.js --mode development",
"test": "echo \"Error: no test specified\" && exit 0"
},
...
}
现在,让我们介绍第二个npm脚本,以实际构建用于生产的应用程序。我们将显式使用Webpack而不是Webpack Dev Server来捆绑所有JavaScript文件,重用以前的相同Webpack配置,还介绍了生产模式:
{
...
"scripts": {
"start": "webpack serve --config ./webpack.config.js --mode development",
"build": "webpack --config ./webpack.config.js --mode production",
"test": "echo \"Error: no test specified\" && exit 0"
},
...
}
如果运行npm run build
,您将看到Webpack如何为您捆绑所有文件。一旦脚本经历了成功,你可以看到 DIST / bundle.js 在飞行中不生成的文件,但在你真正的创建 DIST / 文件夹。
剩下的唯一事情就是现在将 dist / 文件夹上传到Web服务器。但是,为了在本地检查 dist / 文件夹是否具有在远程Web服务器上运行应用程序所需的一切,请使用本地Web服务器亲自进行尝试:
npx http-server dist
它应该输出一个URL,您可以在浏览器中访问它。如果一切正常,您可以将 dist / 文件夹及其内容上载到Web服务器。
另请注意,Webpack开发和生产模式具有其自己的默认配置。开发模式在创建源代码文件时会考虑改善的开发人员体验,而生产版本会对源代码进行所有优化。
每次运行npm run build
,您都会看到Webpack使用 dist / bundle.js 文件创建新版本的bundle JavaScript源代码。最终,您的Webpack构建管道将变得更加复杂,并且最终在 dist / 文件夹中包含两个以上的文件。突然,文件夹变得一团糟,因为您不知道哪些文件属于最新版本。最好的办法是,在每个Webpack构建中都从一个空的 dist / 文件夹开始。
假设我们要在每个Webpack构建中擦除 dist / 文件夹。这将意味着我们自动生成的 dist / bundle.js 文件将被删除(好),而我们手动实现的 dist / index.html 文件将被删除(不好)。我们不想为每个Webpack构建都手动重新创建此文件。为了自动生成 dist / index.html 文件,我们可以使用Webpack插件。首先,从项目的根目录安装html-webpack- plugin插件作为dev依赖项:
npm install --save-dev html-webpack-plugin
成功安装后,在Webpack webpack.config.js 文件中引入Webpack插件:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: path.resolve(__dirname, './src/index.js'),
module: {
rules: [
{
test: /\.(js)$/,
exclude: /node_modules/,
use: ['babel-loader'],
},
],
},
resolve: {
extensions: ['*', '.js'],
},
output: {
path: path.resolve(__dirname, './dist'),
filename: 'bundle.js',
},
plugins: [new HtmlWebpackPlugin()],
devServer: {
contentBase: path.resolve(__dirname, './dist'),
},
};
现在,npm run build
再次运行,看看它如何自动生成一个新的 dist / index.html 文件。它带有一个默认模板,用于说明文件的结构方式和文件中应包含的内容。但是,如果要为 dist / index.html 文件提供自定义内容,则可以自己指定模板:
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
...
plugins: [
new HtmlWebpackPlugin({
title: 'Hello Webpack bundled JavaScript Project',
template: path.resolve(__dirname, './src/index.html'),
})
],
...
};
然后,在您的源代码文件夹中创建一个新的 src / index.html 模板文件,并为其提供以下内容:
<!DOCTYPE html>
<html>
<head>
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<div>
<h1><%= htmlWebpackPlugin.options.title %></h1>
<div id="app">
</div>
</body>
</html>
请注意,您不再需要使用 bundle.js 文件指定script标记,因为Webpack会自动为您引入脚本标记。还要注意,您不一定需要id
属性和div容器,但在上一教程中我们已使用它在其上执行一些JavaScript。
现在,npm run build
再次运行,查看新的自动生成的 dist / index.html是否 与 src / index.html 中的模板匹配。最后,我们已经能够使用Webpack自动创建 dist / bundle.js 和 dist / index.html 这两个文件。这意味着我们可以在每个Webpack版本中删除 dist / 文件夹中的内容。为此,请引入clean-webpack- plugin插件:
npm install --save-dev clean-webpack-plugin
然后在您的 webpack.config.js 文件中引入它:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
...
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title: 'Hello Webpack bundled JavaScript Project',
template: path.resolve(__dirname, './src/index.html'),
}),
],
...
};
现在,每个Webpack构建都将擦除 dist / 文件夹的内容,然后从头开始创建新的 dist / index.html 和 dist / bundle.js 文件。通过这种方式进行设置,您将永远不会在 dist / 文件夹中找到来自较旧Webpack构建的文件,这非常适合仅将整个 dist / 文件夹投入生产。
注意:如果使用的是GitHub之类的版本控制系统,则可以将构建文件夹(dist /)放入.gitignore文件中,因为无论如何,所有内容都是自动生成的。某人获得您的项目的副本后,该人可以执行npm run build
以生成文件。
Webpack捆绑了所有JavaScript源代码文件。但这很完美,但是它给我们作为开发人员带来了一个陷阱。一旦引入了错误并在浏览器的开发人员工具中看到了该错误,通常很难跟踪该错误发生的文件,因为Webpack将所有内容捆绑到一个JavaScript文件中。例如,假设我们的 src / index.js 文件从另一个文件导入了一个函数并使用了它:
import sum from './sum.js';
console.log(sum(2, 5));
在我们的 src / sum.js中 ,我们导出了此JavaScript函数,但不幸的是其中引入了一个错字:
export default function (a, b) {
return a + c;
};
如果npm start
在浏览器中运行并打开该应用程序,则应该在开发人员工具中看到发生的错误:
sum.js:3 Uncaught ReferenceError: c is not defined
at eval (sum.js:3)
at eval (index.js:4)
at Module../src/index.js (bundle.js:457)
at __webpack_require__ (bundle.js:20)
at eval (webpack:///multi_(:8080/webpack)-dev-server/client?:2:18)
at Object.0 (bundle.js:480)
at __webpack_require__ (bundle.js:20)
at bundle.js:84
at bundle.js:87
如果单击发生错误的 sum.js 文件,则只会看到Webpack的捆绑输出。在此示例的情况下,它仍然可读,但是请想象输出一个更复杂的问题:
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = (function (a, b) {
return a + c;
});
;
进一步执行此步骤,并在您的Webpack构建中引入该错误以进行生产。运行,npm run build
然后npx http-server dist
再次在浏览器中看到错误:
bundle.js:1 Uncaught ReferenceError: c is not defined
at Module.<anonymous> (bundle.js:1)
at t (bundle.js:1)
at bundle.js:1
at bundle.js:1
这次,它隐藏在 bundle.js 文件中,而没有让您知道导致它的实际文件。此外,一旦单击 bundle.js 文件,您只会看到Webpack捆绑的JavaScript生产版本,其格式不是可读的。
总之,这不是一个很好的开发人员体验,因为Webpack捆绑的JavaScript文件查找错误变得更加困难。对于开发模式,这是正确的,但对于生产模式,则更是如此。
为了克服此问题,可以引入 source map,以为Webpack提供对原始源代码的引用。通过使用 source map,Webpack可以将所有捆绑的源代码映射回原始源。在您的 webpack.config.js 文件中,为 source map引入一种常见配置:
module.exports = {
...
devtool: 'source-map',
};
之后,与仍然在源代码中的bug,运行npm run build
和npx http-server dist
试。在浏览器中,请注意如何将错误跟踪到导致文件 sum.js :
sum.js:2 Uncaught ReferenceError: c is not defined
at Module.<anonymous> (sum.js:2)
at t (bootstrap:19)
at bootstrap:83
at bootstrap:83
单击文件将为您提供实际的源代码和错误的位置,即使Webpack捆绑了所有JavaScript源代码也是如此。还要注意,有一个名为 dist / bundle.js.map 的新文件,该文件用于执行 src /中的 实际源代码和 dist / bundle.js中 的捆绑JavaScript的 映射 。
到目前为止,我们已经使用一种通用的Webpack配置进行开发和生产。但是,我们也可以为每种模式介绍一个配置。在 package.json中 ,将启动脚本和构建脚本更改为以下内容:
{
...
"scripts": {
"start": "webpack serve --config ./webpack.dev.js",
"build": "webpack --config ./webpack.prod.js",
"test": "echo \"Error: no test specified\" && exit 0"
},
...
}
现在创建这两个新文件,将旧的 webpack.config.js 配置复制并粘贴到两个文件中,然后删除旧的 webpack.config.js 文件。接下来,由于我们在npm脚本中省略了Webpack模式,因此请为每个Webpack配置文件再次介绍它们。首先, webpack.dev.js 文件:
module.exports = {
mode: 'development',
...
};
其次, webpack.prod.js 文件:
...
module.exports = {
mode: 'production',
...
};
您用于启动和构建应用程序的npm脚本应该可以再次工作。但是您可能会想:现在有什么区别?除了我们之前动态传递的Webpack模式外,Webpack的配置对于开发和生产都是相同的。我们甚至引入了不必要的重复。稍后再详细介绍后者。
在不断增长的Webpack配置中,您将介绍在开发和生产中行为应有所不同的内容(例如,插件,规则, source map)。例如,让我们来看一下我们先前实现的 source map。为大型代码库创建 source map文件是一项性能沉重的过程。为了使开发构建快速有效地运行,以提供出色的开发人员体验,您希望开发中的 source map不像生产构建中的 source map那样100%有效。为开发模式创建它们应该更快。因此,您可以对 webpack.dev.js 文件进行首次更改,而该更改不会反映在生产配置中:
...
module.exports = {
mode: 'development',
...
devtool: 'eval-source-map',
};
现在,对于您的开发和生产模式, source map的生成方式有所不同,因为在两个Webpack配置文件中以不同的方式定义了 source map。这只是在开发和生产中为Webpack配置不同配置的一个实例。
目前,用于开发和生产的Webpack配置文件共享许多常用配置。如果我们能够将通用配置提取到一个单独的但常用的文件中,而仅根据开发和生产选择额外的特定配置,该怎么办?让我们通过调整 package.json 文件来做到这一点:
{
...
"scripts": {
"start": "webpack serve --config build-utils/webpack.config.js --env env=dev",
"build": "webpack --config build-utils/webpack.config.js --env env=prod",
"test": "echo \"Error: no test specified\" && exit 0"
},
...
}
如您所见,我们为两个npm脚本引用了一个新的共享 webpack.config.js 。该文件位于新的 build-utils 文件夹中。为了稍后在Webpack配置中区分正在运行的脚本,我们还向配置传递了一个环境标志(dev,prod)。
现在,再次创建共享的 build-utils / webpack.config.js 文件,但这一次是在新的专用 build-utils 文件夹中,并为其进行以下配置:
const { merge } = require('webpack-merge');
const commonConfig = require('./webpack.common.js');
module.exports = ({ env }) => {
const envConfig = require(`./webpack.${env}.js`);
return merge(commonConfig, envConfig);
};
您可以看到该函数env
从npm脚本接收了我们的环境标志。这样,我们可以动态地需要一个具有JavaScript模板文字的特定于环境的Webpack配置文件,并将其与通用的Webpack配置合并。为了合并它,让我们安装一个小助手库:
npm install --save-dev webpack-merge
接下来,我们现在必须在 build-utils 文件夹中实现三个文件:
让我们从新的 build-utils / webpack.common.js 文件中的共享Webpack配置开始:
const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: path.resolve(__dirname, '..', './src/index.js'),
module: {
rules: [
{
test: /\.(js)$/,
exclude: /node_modules/,
use: ['babel-loader']
}
]
},
resolve: {
extensions: ['*', '.js']
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title: 'Hello Webpack bundled JavaScript Project',
template: path.resolve(__dirname, '..', './src/index.html'),
})
],
output: {
path: path.resolve(__dirname, '..', './dist'),
filename: 'bundle.js'
},
devServer: {
contentBase: path.resolve(__dirname, '..', './dist'),
},
};
请注意,与以前的Webpack配置相比,某些文件路径已更改,因为我们现在在专用文件夹中拥有此文件。还要注意,没有Webpack模式,也没有 source map。这两个选项将成为其专用Webpack配置文件中特定的环境(例如,开发,生产)。
通过创建 build-utils / webpack.dev.js 文件 继续进行 操作,并为其提供以下内容:
module.exports = {
mode: 'development',
devtool: 'eval-source-map',
}
最后但并非最不重要的是,新的 build-utils / webpack.prod.js 文件接收以下内容:
module.exports = {
mode: 'production',
devtool: 'source-map',
};
您的文件夹结构现在应该类似于以下内容。请注意,先前各节的 build-utils / 文件夹之外没有Webpack配置:
- build-utils/
-- webpack.common.js
-- webpack.config.js
-- webpack.dev.js
-- webpack.prod.js
- dist/
-- bundle.js
-- bundle.js.map
-- index.html
- src/
-- index.html
-- index.js
- package.json
- .babelrc
您的npm start
和npm run build
脚本现在应该可以工作了。相对于其 build-utils / webpack.dev.js 和 build-utils / webpack.prod.js 配置文件,两者都针对Webpack模式和 source map使用不同的配置。但是它们也共享来自 build-utils / webpack.common.js 的通用Webpack配置。一切都动态合并在 build-utils / webpack.config.js 文件中,该文件根据 package.json中 npm脚本中的传入标志进行动态合并。
有时您可能想在源代码中知道您是处于开发还是生产模式。对于这些情况,您可以通过Webpack指定动态环境变量。由于每个环境都有一个Webpack配置文件(开发,生产),因此可以为它们定义专用的环境变量。在您的 build-utils / webpack.dev.js中 ,通过以下方式定义环境变量:
const { DefinePlugin } = require('webpack');
module.exports = {
mode: 'development',
plugins: [
new DefinePlugin({
'process.env': {
'NODE_ENV': JSON.stringify('development'),
}
}),
],
devtool: 'eval-source-map',
};
这同样适用于您的 build-utils / webpack.prod.js 文件,但具有不同的环境变量:
const { DefinePlugin } = require('webpack');
module.exports = {
mode: 'production',
plugins: [
new DefinePlugin({
'process.env': {
'NODE_ENV': JSON.stringify('production'),
}
}),
],
devtool: 'source-map',
};
现在,您可以使用 src / index.js 文件中console.log(process.env.NODE_ENV);
的环境变量或 src / 文件夹中的任何其他JavaScript来基于它进行决策。在这种情况下,您已经创建了两个不同的环境变量- 每个都针对Webpack模式。但是,将来您可能会为某些情况引入更多的环境变量。
以前,您开始在Webpack配置文件中定义环境变量。但是,这不是敏感信息的最佳实践。例如,假设您要根据开发或生产环境使用API密钥/秘密(凭证)来访问数据库。您不想在您的Webpack配置中公开这些敏感信息,而这些信息可能会与其他人共享。相反,您想为环境文件引入专用文件,这些文件可以与其他文件和版本控制系统(如Git或SVN)保持距离。
让我们从为开发和生产模式创建两个环境变量文件开始。第一个用于开发模式,称为 .env.development 。将其放入具有以下内容的项目的根目录中:
NODE_ENV=development
第二个称为 .env.production 并具有其他内容。它还放置在项目的根目录中:
NODE_ENV=production
通过使用dotenv- webpack插件,您可以将这些环境变量复制到Webpack配置文件中。首先,安装插件:
npm install dotenv-webpack --save-dev
其次,在开发模式的 build-utils / webpack.dev.js 文件中使用它:
const path = require('path');
const Dotenv = require('dotenv-webpack');
module.exports = {
mode: 'development',
plugins: [
new Dotenv({
path: path.resolve(__dirname, '..', './.env.development'),
})
],
devtool: 'eval-source-map',
};
第三,在生产模式的 build-utils / webpack.prod.js 文件中使用它:
const path = require('path');
const Dotenv = require('dotenv-webpack');
module.exports = {
mode: 'development',
plugins: [
new Dotenv({
path: path.resolve(__dirname, '..', './.env.production'),
})
],
devtool: 'eval-source-map',
};
现在,您可以通过 .env.development 和 .env.production 文件在环境变量中引入敏感信息(例如IP地址,帐户凭据和API密钥/秘密)。您的Webpack配置将复制它们,以使其在您的源代码中可访问(请参阅上一节)。如果您使用的是版本控制系统(例如Git),请不要忘记将这些新的 .env 文件添加到您的 .gitignore中 ,以向第三方隐藏您的敏感信息。
Webpack具有庞大的插件生态系统。通过使用Webpack开发或生产模式已经隐式使用了其中的几个。但是,还有其他Webpack插件可以改善您的Webpack捆绑包体验。例如,让我们介绍可用于分析和可视化Webpack捆绑包的加载项。在 package.json中 ,为您的构建过程引入一个新的npm脚本,但是这次使用Webpack插件:
{
...
"scripts": {
"start": "webpack serve --config build-utils/webpack.config.js --env env=dev",
"build": "webpack --config build-utils/webpack.config.js --env env=prod",
"build:analyze": "npm run build -- --env addon=bundleanalyze",
"test": "echo \"Error: no test specified\" && exit 0"
},
...
}
请注意,这个新的npm脚本如何运行另一个npm脚本,但是具有附加配置(此处是Webpack插件)。但是,Webpack插件不会神奇地运行。在这种情况下,它们仅作为标志传递给我们的Webpack配置。让我们看看如何在 build-utils / webpack.config.js 文件中使用它们:
const { merge } = require('webpack-merge');
const commonConfig = require('./webpack.common.js');
const getAddons = (addonsArgs) => {
const addons = Array.isArray(addonsArgs)
? addonsArgs
: [addonsArgs];
return addons
.filter(Boolean)
.map((name) => require(`./addons/webpack.${name}.js`));
};
module.exports = ({ env, addon }) => {
const envConfig = require(`./webpack.${env}.js`);
return merge(commonConfig, envConfig, ...getAddons(addon));
};
现在,不仅合并了通用的和特定于环境的Webpack配置,而且还合并了可选的附加组件,我们将它们放入专用的 build-utils / addons 文件夹中。让我们从 build-utils / addons / webpack.bundleanalyze.js 文件开始:
const path = require('path');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
module.exports = {
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: 'static',
reportFilename: path.resolve(
__dirname,
'..',
'..',
'./dist/report.html'
),
openAnalyzer: false,
}),
],
};
接下来,在命令行上通过npm安装Webpack插件:
npm install --save-dev webpack-bundle-analyzer
如您所见,您已经在新的 build-utils / addons / 文件夹中引入了一个特定的Webpack插件,可以选择添加该Webpack插件。插件文件的命名与 package.json中 npm脚本传递的标志匹配。您的Webpack合并确保将所有传递的插件标记添加为Webpack配置中的实际插件。
现在,请自己尝试用于Webpack分析和可视化的可选工具。在命令行上,键入npm run build:analyze
。然后,检查您的 dist / 文件夹中是否有新文件。您应该找到一种可以通过以下方式打开的方式:
npx http-server dist
,访问URL,然后附加 /report.html您将看到具有两种不同可视化效果的构建优化的Webpack捆绑包。您的应用程序中没有很多代码,但是一旦您在节点包管理器中引入了更多的源代码和更多的外部库(依赖项),您将看到Webpack包的大小将如何增长。最终,您将偶然地引入一个大型库,这会使您的应用程序变得太大。然后,分析工具和可视化工具都可以帮助您找到问题的根源。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。