webpack的核心是一切皆模块,所以它其实本质上就是个静态模块打包器。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图,其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。官网显示的这幅图很形象地描述了这个过程: webpack4相比于3做了很多优化,最大的改变就是支持了零配置打包,不再强制要求必须进行繁琐的webpack配置。 webpack4 新增了一个 mode 配置项。Mode 有两个值:development 或者是 production,默认值是 production。webpack4 针对不同的mode提供了不同的默认配置,这对于只希望配置打包出入口,不想深入了解其他配置的开发人员,提供了最基础的打包优化。当然entry,output ,mode这些配置项也都有默认值,mode默认为production。不同mode的区别与默认配置可以参考https://segmentfault.com/a/1190000013712229那么接下来我们来我们从零开始一步步完成一个完整项目的配置,每部分配置除了会列出基础配置,还会给出一些额外需要注意的事项,也是我在项目中的踩坑总结。 先贴一下项目目录结构: - src
- common 公用代码库
- pages
- [活动名称]\_[h5|pc]
- index.js
- index.html
复制代码 一、多页面入口配置首先我们看看项目的打包入口如何配置: webpack打包入口支持但入口和多入口,但入口文件只限于js文件(据说webpack5在考虑增加HTML文件和CSS文件作为入口)。 多入口时,给entry传入对象即可,如下所示, 其中对象的key值则是入口的name:
显然,我们的项目页面数量是未知的,将所有页面都枚举在配置里显然是不合理的,所以可以定义 const webpack = require('webpack');
const glob = require('glob');
function getEntry() {
const entry = {};
//读取src目录所有page入口
glob.sync('./src/pages/*/*/index.js')
.forEach(function (filePath) {
var name = filePath.match(/\/pages\/(. )\/index.js/);
name = name[1];
entry[name] = filePath;
});
return entry;
};
module.exports = {
mode: 'development',
// 多入口
entry: getEntry(),
}
复制代码 二、打包输出配置无论是单入口还是多入口,都只能指定一个输出配置。我们看看项目的
通常,dev环境时,不用配置publicPath,此时静态资源的引用路径相对于HTML页面。而生产环境时,把publicPath的值设为CDN的目录路径就可以了。 这里配置有几点需要注意的: 1、动态publicPath这里说了是多端多页面项目,多端只的就是PC和H5两端,那么这就意味着各端的CDN资源路径是不一样的,所以publicPath值也应该不一样。如何动态设置publicPath呢?
webpack 提供了 __webpack_public_path__ = myRuntimePublicPath; // 一定要写在最顶部
复制代码 2、hash值的区别hash:以项目为维度生成的hash值,项目全部文件都共用一个hash值 chunkhash: 以chunk为维度生成的hash值,不同入口生成不同的chunkhash值 contenthash: 根据资源内容生成的hash值 一般是用chunkhash,contenthash也有使用场合,比如在mini-css-extract-plugin插件配置使用,后面会详细讲到。 三、loader配置配置好了输入输出后,我们就需要来配置对模块内容如何进行处理。webpack 只能理解 JavaScript 和 JSON 文件。loader 让 webpack 能够去处理其他类型的文件,并将它们转换为有效模块。 1、js 模块需要引入babel的话,我们就需要使用babel-loader
使用babel时需要注意,Babel默认只转换新的JavaScript句法(syntax),而不转换新的API,比如Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise等全局对象,以及一些定义在全局对象上的方法(比如Object.assign)都不会转码,如果要使用需要引入polyfill。
引入polyfill 的方式有很多种,这里推荐babel {
'presets': [
[
'env',
{
'browsers': ['last 5 versions', '> 5%', 'Android > 4.3']
}
],
'stage-2'
],
'plugins': [
'transform-runtime'
]
}
复制代码 2、css 模块对于css模块,常用的loader有style-loader和css-loader。
如果你也使用了sass-loader,有个问题可能需要注意。当你的index.scss里@import了其他scss文件比如a.scss时,如果a.scss里使用了url(),且里面的路径是相对路径,那么在sass-loader 处理过后给css-loader处理时就会报错,找不到url()里指定的资源。这是为什么呢? 实际上,当sass-loader处理时,会将index.scss里@import的A.scss合并进来,最后只输出index.scss。但A.scss里的url()本来是以A.scss写的相对路径,这样合并又不对url()做处理的话,就导致了合并后无法定位到url()里的资源。对于这个问题,有两种解决办法:
3、图片、字体等资源对于图片等其他资源,我们一般使用file-loader进行处理,它实现的功能很简单:
{
test: /\.(gif|png|jpe?g|eot|woff|ttf|pdf)$/,
loader: 'file-loader',
},
复制代码 4、import AMD 模块尽管webpack既支持commonjs规范也支持AMD规范。但是我们如何通过import 的方式引入AMD 模块或者其他不支持模块化的库呢? 我们项目里使用到了zepto,这里就以zepto为例,在import zepto时会报错
这就是因为zepto只使用了AMD 规范导出模块。解决所有这类问题其实很简单,只需要使用 {
test: require.resolve('zepto'),
use: ['exports-loader?window.Zepto','script-loader']
}
复制代码
四、plugin 配置插件机制是webpack的核心之一,插件(Plugins)是用来拓展webpack功能的,它们会在整个构建过程中生效,执行相关的任务。我们一般使用插件来完善我们的构建流程,webpack有许多插件可用,这里只挑两个必备插件来详细说明 1、html-webpack-plugin前面有说过,目前webpack的打包入口只支持JS文件,所以它打包输出的也是JS文件,那么如何把这个JS文件引入我们的html中去呢,手动引入无法监测到hash值的变化,肯定是不OK的。因此我们就用到了
2、mini-css-extract-plugin前面使用css loader 和 style-loader对css文件进行处理后,css文件被作为模块也打包在了js文件中。实际生产环境,我们当然是希望js文件和css文件分离的,所以这里就可以使用 module: {
rules: [
{
// 增加对 SCSS 文件的支持
test: /\.scss|\.css/,
// SCSS 文件的处理顺序为先 sass-loader 再 css-loader 再 style-loader
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
publicPath: CDN.css,
},
},
{
loader: 'css-loader',
// 给 css-loader 传入配置项
options: {
importLoaders: 2,
},
},
'postcss-loader',
{
loader: 'sass-loader',
},
],
}
],
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].[contenthash].css',
chunkFilename: '[name].[contenthash].css',
}),
],
复制代码 这里之所以设置为 五、其他配置1、resolveresolve配置规定了webpack如何寻找各个依赖模块。 前面讲到的alias就是在这里配置。在资源引用时,如果资源引用路径太深,又比较常用,我们则可以定义路径别名,例如:
我们就可以直接在代码中这样引用了: import Utility from 'h5/util';
复制代码 2、webpack dev server
启动webpack-dev-server后,在目标文件夹中是看不到编译后的文件的,实时编译后的文件都保存到了内存当中 1) HMR
2) publicPathpublicPath路径下的打包文件可以在浏览器中访问,可以这么理解,webpack-dev-server打包的内容是放在内存中的,这些打包后的资源对外的的根目录就是publicPath。
默认 devServer.publicPath 是 '/',所以你的包(bundle)可以通过 3、配置分离通常,我们本地开发环境和生产环境会采用不同的配置文件,发布上线时,我们会对资源进行压缩、合并等优化,但在本地开发时,为了提高构建速度,方便调试代码,我们则会省去这些优化配置,与此同时,我们更加关注模块热更新、localhost server等等。所以一般会为每个环境编写彼此独立的 webpack 配置,这里项目的webpack配置文件如下,其中webpack.common.js是用来放dev和dist里的公共配置: 这里会用到 module.exports = {
module: {
rules: []
}
};
复制代码
所以我们可以在package.json添加我们的webpack启动命令如下: 'scripts': {
'dist': 'cross-env NODE_ENV=production webpack --config webpack.dist.js',
'dev': 'webpack-dev-server --config webpack.dev.js',
},
复制代码
六、优化到这里,我们项目已经能起来了,但是作为一名合格的程序猿,我们当然要探索更优实践。webpack有哪些常用的优化措施呢? 1、按需加载webpack 提供了两种动态加载的语法。第一种,也是推荐选择的方式是,使用符合 ECMAScript 提案 的 import() 语法 来实现动态导入。第二种,则是 webpack 的遗留功能,使用 webpack 特定的 require.ensure。 import() 会返回一个 promise,在代码中所有被import()的模块,都将打成一个单独的包,在浏览器运行到这一行代码时,就会自动请求这个资源,实现动态加载。 ** 使用import()时应该注意以下几点: **
2、抽离公共模块1)一般项目为了合理利用浏览器缓存,一般会将不常变动的第三方库以及公共代码和业务代码分开打包 所以一般项目的打包策略为:
对于分包方式,webpack 4 移除 CommonsChunkPlugin,取而代之的是optimization.splitChunks 让我们看看这里怎么配置:
注意抽离出来的代码要在HTML文件里引入 2)多端项目由于项目包含两端代码,H5\PC部分依赖是独立的,单纯的从项目层面进行公共模块的抽离是不行的。 所以这里得详细设置公共库和代码的匹配规则。比如我们项目PC用的JQ,H5用的zepto,就可以配置 optimization: {
splitChunks: {
cacheGroups: {
h5common: {
test: /zepto/,
name: 'h5common',
chunks: 'initial',
priority: 1,
minChunks: 1,
},
},
},
},
复制代码 3、优化loader配置配置loader时,我们可以通过exclude设置哪些目录下的文件不进行处理,通过include精确指定只处理哪些目录下的文件,以此来缩小处理范围,加快构建速度。
4、限制路径解析范围当我们引用模块时,如果出现import ‘zepto’这样的依赖引入方式,webpack会默认从当前目录往上逐层查找是否有 resolve: {
modules: [path.resolve(rootDir, 'node_modules')],
},
复制代码 总结这篇文章以多端多页面项目为例,深入讲解了如何初始化项目webpack配置,这些实践不仅适用于这个项目,对于多页面项目和普通项目也同样适用。 |
|
来自: Earlycl6i7o9ek > 《技术》