前端技术巡礼:webpack


同事的一期分享,觉得很有价值,经授权在此发布

一、webpack起源

随着web应用变得更加复杂和庞大,代码难以管理,随之出现了模块化开发,新型框架,新型语言。这些,都无法直接在浏览器中直接运行,或者运行过于繁琐。所以需要一个将这些整合起来的构建工具。
1561362295133.jpg

构建工具主要的作用是需要有进行代码转化、文件优化、模块合并、自动刷新、代码校验和自动发布的功能,其思想就是工程化、自动化在前端开发中的体现。
1561362341828.jpg

那么,各位对于各个构建工具在开发中的体验如何?

从模块模块化工具中可以大致得出web开发的发展趋势。

使用webpack构建项目的原因:

  1. 大多数开发团队紧跟潮流,使用模块化+新语言+新框架,webpack为新项目提供一站式的解决方案。 webpack
  2. 有良好的生态链和维护团队,能提供良好的开发体验并保证质量。
  3. webpack被全世界大量开发者使用和验证,能找到各个层面所需要的教程和经验分享。

二、webpack核心功能

Entry

entry 是webpack打包的入口,从此入口解析出项目所需要的依赖信息。entry 配置是必填的,若不填则将导致 Webpack 报错退出,。
可以有多个页面的入口文件,输出时会输出相应key值的js文件。

entry: { // 用于 webpack 查找启动,其上下文是入口文件所处的目录的绝对路径的字符串。
app: path.resolve(__dirname, ‘../src’) + ‘/index.js’, //JavaScript执行入口文件
search: path.resolve(__dirname, ’../src’) + ’search.js’
}

entry中,主要分三个部分,分别是context,entry类型,chunk。

  • context,默认为当前工作目录,可以修改为其他目录。
  • Entry 类型可以是 string、array、object、function其中之一,或者是相互组合:

    • string:是模块入口路径,可以是相对路径
    • array: 与string相同,需要搭配output.library使用
    • object:用于配置多个页面入口路径
    • function: 用于配置动态入口文件路径,函数需要返回字符串、数组、对象
      图片12.png
  • chunk: 分块,Webpack会为每个生成的Chunk取一个名称,Chunk的名称和entry的配置有关。

    • 如果entry是一个string或array,就只会生成一个Chunk,这时 Chunk 的名称是 main。
    • 如果 entry 是一个object,就可能会出现多个Chunk,这时Chunk的名称是object键值对中键的名称。
      图片13.png

Output

输出结果,在 Webpack 经过一系列处理并得出最终想要的代码后输出结果。需要注意的是,即使可以存在多个入口起点,但只指定一个输出配置,如果使用了多入口,会根据入口文件输出不同的文件。

output: {
    filename: '[name].[hash].js',
    path: path.resolve(__dirname, './../dist')
}

output详细配置
output 是一个 object ,里面包含一系列配置项

  • filename: 输出文件的名称,string, 多个文件可以使用占位符变量。
  • chunkFilename: 无入口的chunk在输出时的文件名称,只用于指定在运行过程中产生的chunk在输出时的文件名称
  • path:配置输出文件存放在本地的目录,必须为绝对路径
  • publicPath:配置发布到线上资源的URL前缀,默认为空,需要注意配置项与引入项要一致,不然会造成404

图片14.png

配置文件内容

图片15.png

输出文件内容

output详细配置 – 导出模块

当用Webpack 去构建一个可以被其他模块导入使用的库时,需要用到 libraryTarget 和library。

  • output . libraryTarget 配置以何种方式导出库。
  • output . library 配置导出库的名称。

output.libraryTarget 是字符串的枚举类型 , 支持以下配置。

  • var(默认):编写的库将通过 var 被赋值给通过 library 指定名称的变量 。
  • commonjs:编写的库将通过 CommonJS 规范导出。
  • commonjs2:编写的库将通过 CommonJS2规范导出。
  • this:编写的库将通过 this 被赋值给通过 library指定的名称。
  • window:编写的库将通过window 赋值给通过 library 指定的名称。
  • global:编写的库将通过 window 赋值给通过 library 指定的名称
  • umd: library 暴露为所有的模块定义下都可运行的方式。

output . libraryExport配置要导出的模块中哪些子模块需要被导出 。libraryTarget需要设置为commonjs或commonjs2

module

module 配置处理模块的规则,具体处理规则是由一个一个的loader来完成。所以其核心功能是loader。

module中的loader

本质上,loader是webpack对于某一类文件的解决方案。

注意:

  • 在使用loader之前,需要通过 npm install 安装相关的loader项目。
  • use 属性的值需要是一个由 Loader 名称组成的数组,Loader 的执行顺序是由后到前的。
  • 每一个 Loader 都可以通过 URL querystring 的方式传入参数,例如 css-loader? url 中的  url  告诉 css-loader 要开启 url() 处理。
  • 除了通过URL querystring 传入,还有就是通过对象的方式传入。

    module: {
      rules: [{
      test: /\.css$/i,
      loaders: [
          'style-loader’,
          'css-loader?sourceMap’
       ]
      }]
    }
    

loader详细配置

loader一般是配置在rules中,rules是一个数组,用来配置模块的读取和解析规则,数组中的每一项都是一个处理规则。

配置rules通过以下规则完成:

  • 条件匹配:通过 test、include、exclude三个配置项选中loader要应用规则的文件。include和exclude都可以是数组,代表了或的关系。
  • 应用规则:对选中的文件通过use配置项来应用loader,可以之应用一个loader或者按照从后向左应用一组loader,同时可以分别向loader传入参数。
  • 重置顺序:一组 Loader 的执行顺序默认是从右到左执 行的, 通过 enforce 选项可以将其中一个Loader的执行顺序放到最前(pre)或者最后(post)。
    图片16.png

module中的其他项

noParse 配置项可以让 Webpack 忽略对部分没采用模块化的文件的递归解析和处理, 这 样做的好处是能提高构建性能。如:jQuery、ChartJS

noParse 是可选的配置项,类型为需要是 RegExp、[RegExp]、function其中之一

parser 属性可以更细粒度地配置 哪些模块语法被解析、哪些不被解析。

noParse 和 parser区别:parser 可以精确到语法层面,而 noParse 只能控制哪些文件不被解析。

图片17.png

resolve
Resolve 配置 Webpack如何寻找模块所对应的文件。webpack内置解析JavaScript模块化语法功能,默认会有相关配置,我们也可以修改。

  • resolve.alias 配置路径别名,是一个对象,key是别名,value是路径
  • resolve.mainFields 优先使用哪份代码
  • resolve.extensions 自动带上后缀,是一组数组,内含文件名后缀
  • resolve.modules 配置 Webpack 去哪些目录下寻找第三方模块,默认为node_modules
  • resolve.descriptionFiles 配置描述第三方模块的文件名称
  • resolve.enforceExtension 引入文件时是否携带后缀,为true必须携带
  • resolve.enforceModuleExtension 与上一个类似,只针对 node_modules

Plugin

Plugin 是用来扩展 Webpack 功能的,通过在构建流程里注入钩子实现,它给 Webpack 带来了很大的灵活性

  • Webpack 是通过 plugins 属性来配置需要使用的插件列表的。
  • plugins 属性是一个数组,里面的每一项都是插件的一个实例,在实例化一个组件时可以通过构造函数传入这个组件支持的配置属性。
  • 同样需要先行安装相应插件
  • plugin本身提供的配置项比较繁琐
    use: ExtractTextPlugin.extract({
            fallback: "style-loader",
            use: "css-loader"
    })

    plugins: [
          new ExtractTextPlugin({
            filename: `[name].[hash].css` // webpack 4 不支持             
         contentHash
      }),
    ]

devServer和热更新

在开发中可能会遇到以下问题

  • 提供 HTTP 服务而不是使用本地文件预览;
  • 监听文件的变化并自动刷新网页,做到实时预览;
  • 支持 Source Map,以方便调试。

为了解决上面的问题,webpack提供了webpack-dev-server的开发工具 

DevServer 会启动一个 HTTP 服务器用于服务网页请求,同时会帮助启动 Webpack ,并接收 Webpack 发出的文件更变信号,通过 WebSocket 协议自动刷新网页做到实时预览。可以通过命令行进行配置,也可以在webpack.config.js中进行配置

npm i -D webpack-dev-server
webpack –watch // 热更新
webpack –watch –hot // 热替换
// 增加sorcemap
webpack –watch –hot --devtool source-map

要配置 DevServer,除了可以通过命令行参数传入,还可以在配置文件里通过 devServer 传入参数。

  • devServer.hot :是否启用模块热替换功能
  • devServer.inline :为入口页面添加“热加载”功能,即代码改变后重新加载页面。
  • devServer.historyApiFallback: 默认禁用,为true则匹配不到的路由会转至主页的index.html,也可以使用rewrites属性详细设置。
  • devServer.contentBase:配置 DevServer HTTP 服务器的文件根目录,默认为根目录。
  • devServer.headers :可以在 HTTP 响应中注入一些 HTTP 响应头。
  • devServer.host: 用于配置 DevServer 服务监听的地址。
  • devServer.port:监听端口配置
  • devServer.allowedHosts: 配置一个白名单列表。
  • devServer.disableHos tCheck: 用于配置是否关闭用于 DNS 重新绑定的 HTTP请求的 HOST 检查。
  • devServer.https: 是否启动https服务,可以通过 key、cert、ca配置HTTPS证书
  • devServer.compress: 是否开启gzip压缩
  • devServer.open: 第一次构建完成后打开的页面

总体打包流程

初始化配置参数 -> 绑定事件钩子回调 -> 确定Entry逐一遍历 -> 使用loader编译文件 -> 输出文件
1561365666689.jpg

  • 若想让源文件加入构建流程中被 Webpack 控制, 则配置 entry;
  • 若想自定义输出文件的位置和名称 ,则配置 output;
  • 若想自定义寻找依赖模块时的策略 ,则配置 resolve;
  • 若想自定义解析和转换文件的策略, 则配置 module,通常是配置module.rules里的Loader;
  • 若其他大部分需求可能通过 Plugin 去实现,则配plugin 。

https://webpack.docschina.org/configuration/

三、webpack常用配置

ES6语言

ES6的配置相关配置是开发中最为常用的配置。我们需要使用babel进行配置。
使用步骤:

npm i -D babel-core babel-preset-env
npm i -D babel-loader@7
// 在项目根目录新建.babelrc文件,并根据规则配置此文件,文件是JOSN格式
在webpack.config.js中配置babel-loader

TypeScript语言

TypeScript是 JavaScript 的一个超集。TypeScript官方提供了可以转为JavaScript的编译器。需要在tsconfig.json中配置。
使用步骤:

npm install -g typescript
// 在项目根目录新建tsconfig.json文件,并根据规则配置此文件
// typescript编译器在将 ES6 语法转换成 ES5 语法时 需要注入辅助函数,所以需要开启编译器的importHelpers选项,配置在tsconfig.json里面
npm install --save-dev typescript ts-loader
// 在rules中配置loader,同时在loader中可以配置devtool,方便调试代码

SCSS语言

SCSS 可以让我们用更灵活的方式写css 。它是一种 css 预处理器,语法和 css 相似,但加入了变量、逻辑等编程元素。

使用步骤:

npm i -D node-sass  #sass-loader 依赖 node-sass
npm i -D sass-loader css-loader style-loader
// 在rules中配置loader,需要注意顺序,sass-loader需要在数组最后

React

React最常用到的就是JSX语法和Class类,所以需要配置jsx和es6的相关loader
使用步骤:

npm install –save react react-dom
// 需要增加 npm i babel-core@^7.0.0-bridge.0 @babel/core regenerator-runtime
// 在ES6的配置中将.babelrc配置文件的preset字段中修改为["@babel/preset-env", "@babel/preset-react"]
// 与typescript的结合则需要将tsconfig.json中的compilerOptions增加react支持,文件后缀需要修改为tsx, 注意,入口文件也需要改成tsx
// 安装新的react依赖npm i react react- dom @types/react @types/react-dom –save
// webpack.config.js中的resolve.extensions增加一个.tsx后缀

HTML

以上的打包代码中只是针对某一部分进行打包,但是最终需要在HTML中引入,项目完成之后的需要对项目进行压缩。对于HTML代码的压缩及配置是通过plugin完成的。

使用步骤:

npm install –D html-webpack-plugin
// 在webpack中引入html-webpack-plugin
// 在plugins中 new htmlWebpackPlugin()同时传入相关参数
// 参数包含了filename、template、chunks …

常用loader

webpack中有很多特别常用的loader。

  • file-loader 主要是用来处理文件,如:图片、字体等等。
  • url-loader 与file-loader作用基本一致,多了一个字节限制,小于limit使用dataurl,大于limit 使用file-loader,通过limit控制。
  • style-loader、css-loader、postcss-loader、scss-loader等等都是用于打包样式
  • raw-loader 和 svg-inline-loader 可以处理svg文件, svg-inline-loader会去除多余的代码,减小体积

四、webpack优化

性能

缩小文件的搜索范围,webpack在启动时会从entry中递归寻找依赖,速度很快,但是文件过多时,也会造成查找时间过长。

  • 优化loader配置,loader处理文件耗时较长,所以要尽量少的处理文件,可以通过 test 、 include 、 exclude 三个配置 项来命中 Loader 要应用规则的文件。
  • 优化 resolve.modules 配置,此配置项用于配置weback要在哪里查找依赖,默认为node_modules,如果已经确定安装有第三方库,可以直接指定相应文件,减少查找时间
  • 优化 resolve.mainFields 配置,此配置用于配置第三方模块入口使用哪个文件,第三方库因为运行环境的原因,可能会有多个入口文件, 大部分第三方库都是以main开始,直接指定查找main,可以减少查询时间
  • 优化 resolve.alias 配置,此项是通过别名来将原导入路径映射成一个新的导入路径。大多数库被发布到 Npm 仓库中时都会包含打包好的完整文件,直接引入可以减少耗时的递归操作。
  • 优化resolve.extensions 配置,此项可以在开发中省去输入文件后缀,但是在打包过程中,webpack会首先查询文件是否存在,如果列表中的后缀过多,会导致尝试次数过多,影响性能
  • 优化 module. noParse配置,此项可以对没有采用模块化的库忽略解析,这样增加了构建的性能

使用Dllplugin,Webpack 己经内置了对动态链接库的支持,需要通过以下两个内置的插件接入 。
• DllPlugin 插件 :用于打包出一个个单独的动态链接库文件 。
• DllReferencePlugin 插件:用 于在主要 的配置文件中引入 DllPlugin 插件打包好的动态链接库文件。

使用 HappyPack,它将任务分解给多个子线程去并发执行, 子线程处理完后再将结果发送给主进程。

const HappyPack =require (’happypack‘);
loader中 use: [‘happypack/loader? id=babel’]
plugin中 new happyPack({id: ‘babel’, loaders: …})

使用 ParallelUglifyPlugin,webpack在打包用于线上代码时,会有代码压缩的过程,这个过程通常是通过UglifyJS进行处理,如果代码量大,单线程执行就会造成卡顿

ParallelUglifyPlugin利用的多线程并行思想,本质还是通过UglifyJS进行压缩


希望你能从本篇文章获得收获,更加熟练的使用webpack~
let's hack!

声明:GodGc's World|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA协议进行授权

转载:转载请注明原文链接 - 前端技术巡礼:webpack


軟件工程沒有銀彈