关于webpack:前端工程化篇三-席卷八荒Webpack基础

6次阅读

共计 19803 个字符,预计需要花费 50 分钟才能阅读完成。

字数:8960,浏览工夫:28 分钟,点击浏览原文

尽前行者境地窄,向后看者眼界宽。——《格言联璧·持躬类》

【前端工程化】系列文章链接:

  • 01 扬帆起航 - 开发环境
  • 02 白璧微瑕 - 包管理器

示例代码仓库:https://github.com/BWrong/dev-tools

申明:本篇文章基于 webpack v4.43.0,如依照文中代码执行报错,请先查看依赖版本是否和示例代码仓库中统一。

前言

自 Web2.0 以来,前端技术日益蓬勃发展,前端仔们不再满足于切切页面、写写动画,而是可能做更多 ” 高大上 ” 的事件了。但随着我的项目规模和复杂度的晋升,代码的依赖保护、代码压缩、代码格调审查等与业务无关但又不得不做的事件占据了开发人员越来越多的工夫。那时,这些操作均只能依附开发人员手动来进行解决,耗时耗力,齐全是一个刀耕火种的时代(从前车马很慢,毕生只够爱一个人????)。

起初,NodeJS 呈现了,作为一门服务端语言,它领有更加弱小的能力,尤其是解决文件的能力,运行也不再受限于浏览器沙盒,能够间接在终端命令行运行。这些个性正是开发前端工程化的外围需要,所以有人开始借助 NodeJS 来做那些耗时耗力的工作,属于前端本人的工程化时代初见端倪。

当然,这里咱们的重点是 Webpack,所以不会花大量篇幅去讲述前端工程化的发展史,仅仅列出一些比拟有代表性的工具,以致敬这些前浪们

  • Grunt:基于工作的命令行构建工具,构建工具的先驱。
  • Gulp:管道,流式解决,构建性能比 grunt 高,配置也比较简单。
  • Browserify:把 Commonjs 打包成浏览器反对的包。
  • Webpack:模块打包器,通过 loader 反对泛滥文件类型,反对插件,反对按需加载,提取专用代码等,生态欠缺,目前最风行的打包工具。
  • Rollup:侧重于打包库、SDK,输入成绩体积较小。
  • Parcel:打包速度快,入口反对 html,打包时会主动装置须要的插件,人家的口号是技术零配置。
  • snowpack:打包速度快,无需打包工具。

在 Webpack 刚刚进去的时候,那个时候 Gulp 和 Grunt 还风华正茂,在网上常常有人拿它们来做比照,其实他们是不同类型的构建工具,不是太具备可比性。

如上图所示,虽说它们都是构建工具,然而 Gulp、Grunt 更加偏差工作式,所有的操作均需以工作的形式来构建;而 Webpack 则是模块化的编译计划,它通过依赖剖析,进行模块打包,与它比照的应该是 Browserify,Rollup 之流。

目前来说,grunt 和 gulp,曾经功成身退了,当下 webpack 无疑是最热门、最弱小的打包工具。这都得益于保护团队海纳百川有容乃大的态度,Rollup 的 tree shaking、Parcel 的零配置这些亮点都被 webpack 排汇了。兴许有的人感觉是剽窃,然而咱们读书人的世界何来剽窃呢。更何况不是这样的话,不是得学更多的工具了,又何来我这一头漆黑靓丽的秀发呢?????????

好了,啰嗦了半天,该进入主题了,接下来,看看 webpack 官网的定义:

At its core, webpack is a static module bundler for modern JavaScript applications. When webpack processes your application, it internally builds a dependency graph which maps every module your project needs and generates one or more bundles.

译:Webpack 是一个古代 JavaScript 应用程序的 动态模块打包器 module bundler),当 webpack 解决应用程序时,它会递归地构建一个 依赖关系图(dependency graph),其中蕴含应用程序须要的每个模块,而后将所有这些模块打包成一个或多个 bundle

这里屡次提到了模块,模块在前端开发中个别指 JavaScript 的模块化产物,相干的介绍网上也有很多,切实找不到也能够看看鄙人之前的文章再谈 JavaScript 模块化,这里咱们不再赘述。然而这里 Webpack 所指的模块不仅仅是 JavaScript 中的模块,通过 Loader,它能够解决任意类型的资源。狭义上来说,在 Webpack 看来,任意资源都是模块。

装置与配置

当初 Webpack 最新的版本是 4.43.0(留神当初 5.0 已公布,默认装置会是 5.0),能够装置在全局,也能够装置到我的项目,这里举荐在我的项目本地装置。

npm init -y  # 初始化 npm 配置文件
npm install --save-dev webpack # 装置外围库
npm install --save-dev webpack-cli # 装置命令行工具

备注: webpack4.0 后,将外围和 cli 局部拆离开了,所以两个都须要装置,拆离开的益处就是外围局部能够在 nodejs 的我的项目中应用,不再受限于命令行环境,更加合乎职责繁多准则。

受 Parcel 的“刺激”,Webpack 从 4.0 开始反对零配置,开箱即用,默认会应用 /src/main.js 作为 entry,/dist/main.js作为输入成绩。

建设如下文件:

- src
  |- index.js
// index.js
console.log('hello webpack');

而后执行npx webpack,就能够看到打包后果:

在输入信息中,显示了本次打包的 hash 值、webpack 版本、耗时等,还列出了打包的每个模块信息,蕴含资源名称、大小、chunk(前面会详解)等信息,另外在 src 同级目录会生成一个 dist 目录,上面会有一个 main.js,即是打包成绩,在index.html 中间接引入该文件就能够了。

下面,其实咱们曾经胜利实现了一个文件的打包,是不是很简略。不过认真看看命令行输入的信息中,前面是有一大段正告的,作为一个有谋求的程序猿,怎么可能让这种事件产生呢!究其原因,其实在 webpack4.0 中,倡议在打包的时候传入 mode,来告知其打包的指标环境是开发环境还是生产环境(不设置默认为 production),以便它外部来做相应的优化,能够通过 --mode 参数来指定模式,如npx webpack --mode=production,这样就不会有正告了。

咱们能够试试将 mode 设置为 development 后再打包一次,看看成绩有什么不同(答案:development 下代码未进行压缩)。

当然,也能够在命令行中配置参数来扭转 Webpack 的打包设置,具体的用法能够查看 Command Line Interface,罕用的配置都能够通过命令行来配置,例如默认 webpack 会查找我的项目根目录下的 webpack.config.js,咱们能够通过webpack --config ./build/webpack.config.js 来指定配置文件,在日常的开发中,个别都是通过配置文件来应用的,能够实现更加简单的设置,而且更加不便。

外围概念

在开始前,有必要理解几个外围概念:

  • chunk:指代码块,一个 chunk 可能由多个模块组合而成,也用于代码合并与宰割。
  • bundle:资源通过 Webpack 流程解析编译后最终结输入的成绩文件。
  • entry:顾名思义,就是入口终点,用来通知 webpack 用哪个文件作为构建依赖图的终点。webpack 会依据 entry 递归的去寻找依赖,每个依赖都将被它解决,最初输入到打包成绩中。
  • output:output 配置形容了 webpack 打包的输入配置,蕴含输入文件的命名、地位等信息。
  • loader:默认状况下,webpack 仅反对 .js 文件,通过 loader,能够让它解析其余类型的文件,充当翻译官的角色。实践上只有有相应的 loader,就能够解决任何类型的文件。
  • plugin:loader 次要的职责是让 webpack 意识更多的文件类型,而 plugin 的职责则是让其能够管制构建流程,从而执行一些非凡的工作。插件的性能十分弱小,能够实现各种各样的工作。
  • mode:4.0 开始,webpack 反对零配置,旨在为开发人员缩小上手难度,同时退出了 mode 的概念,用于指定打包的指标环境,以便在打包的过程中启用 webpack 针对不同的环境下内置的优化。

webpack 的配置较多,接下来,仅对罕用配置做一些理解,残缺的配置能够查阅 webpack-options。

阐明:接下来的内容,应用的命令均应用后面一篇介绍的 npm script 来定义,而不用再每次都输出 npx。

外围配置

mode

指定打包的模式和环境,取值为development, productionnone 之中的一个,能够启用 webpack 内置在相应环境下的优化。其默认值为 production

module.exports = {mode: 'production'};

或者通过命令行配置:

webpack --mode=production
选项 形容
development 会将 DefinePluginprocess.env.NODE_ENV 的值设置为 development。启用 NamedChunksPluginNamedModulesPlugin
production 会将 DefinePluginprocess.env.NODE_ENV 的值设置为 production。启用 FlagDependencyUsagePlugin(增加依赖标识), FlagIncludedChunksPlugin(给 chunk 增加 id), ModuleConcatenationPlugin(解决模块作用域), NoEmitOnErrorsPlugin(防止生成异样的代码), OccurrenceOrderPlugin(按次数进行模块排序), SideEffectsFlagPlugin(解决 Side Effects 模块标识)和 TerserPlugin(js 压缩)。
none 不应用任何默认优化选项

提醒:对于各选项 webpack 外部默认的具体配置,能够查看该文档。

context

用于指定根底目录,用于从配置中解析入口终点和 loader,须为绝对路径,默认为启动 webpack 的工作目录。

module.exports = {context: path.resolve(__dirname, 'app')
};

entry

打包的入口文件,个别为利用的入口,不便 webpack 查找并构建依赖图。

module.exports = {entry: "./app/entry", // 如果仅有一个入口,能够简写为此形式,为 entry:{main:"./app/entry"}的简写
    entry: ["./app/entry1", "./app/entry2"], // 为 entry:{main:["./app/entry1", "./app/entry2"]}的简写
    entry: { // 多入口的形式,每项即为一个 chunk,key 为 chunkName
        a: "./app/entry-a",
        b: ["./app/entry-b1", "./app/entry-b2"]
    }
}

output

形容了 webpack 如何输入,值为一组选项,蕴含了输入文件名字、地位等信息。

module.exports = {
  output: {filename: '[name]_[chunkhash:8].bundle.js',
       path: path.resolve(__dirname, 'dist')
  }
};
  • output.filename:配置输入文件的名字,对于单入口的状况,须要设定为一个指定的名称,对于多入口的状况,能够应用占位符模板来指定。
模板 形容
[hash] 模块标识符 (module identifier) 的 hash
[chunkhash] chunk 内容的 hash
[name] 模块名称
[id] 模块标识符(module identifier)
[query] 模块的 query,例如,文件名 ? 前面的字符串
[function] 能够应用函数动静返回 filename

[hash][chunkhash] 的长度能够应用 [hash:16](默认为 20)来指定。或者,通过指定output.hashDigestLength 在全局配置长度。

  • output.path:配置输入目录,值为绝对路径。
  • output.publicPath:如果须要援用内部资源,能够通过此配置设置资源的地址,例如部署要将资源上传到 CDN 服务器,就须要填上 CDN 的地址。

如果打包一个类库或者 sdk,可能还须要设置 librarylibraryExportlibraryTargetumdNamedDefine 等选项来配置类库裸露的名字及兼容的模块标准等,可查看官网指南。

loader

loader 用于对模块的源代码进行转换,在 import 或 ” 加载 ” 模块时解析文件,通过增加 loader 能够让 webpack 解决多种类型的文件。

module.exports = {
  module: {
    rules: [
      { 
        test: /\.css$/, 
           use: [{ loader: 'style-loader'},
          {
            loader: 'css-loader',
            include: './src/assets', // 指定查找的目录,resource.include 的简写
            // exclude:'', // 指定排除的目录,resource.exclude 的简写
            options: {modules: true}
          }
        ]
      },
      {test: /\.ts$/, use: 'ts-loader'}
    ]
  }
};

loader 能够在配置文件的 module.rules 属性中设置,能够配置多个规定,每个规定通过 test 属性设置匹配的文件类型,在 use 属性中指定对应的 loader 及其对应的配置(options)。

对于匹配条件,webpack 提供了多种配置模式:

  • {test: ...} 匹配特定条件
  • {include: ...} 匹配特定门路
  • {exclude: ...} 排除特定门路
  • {and: [...] }必须匹配数组中所有条件
  • {or: [...] } 匹配数组中任意一个条件
  • {not: [...] } 排除匹配数组中所有条件

留神:同一个规定能够指定多个 loader,从右到左 链式传递,顺次对文件进行解决,当然能够通过enforce(可取值为pre | post,别离为前置和后置)强制扭转执行程序。

通过 loader 咱们能够在 js 文件中导入 css、img 等文件,实践上能够实现解析各类文件,罕用的 loader 能够在官网 -Loaders 中查问。

plugin

插件的目标在于解决 loader 无奈实现的其余事。插件的实质其实是一个具备 apply 办法的 JavaScript 对象,该办法会被webpack compiler 调用,并且 compiler 对象可在 整个 编译生命周期拜访,用于自定义构建过程。

在配置文件的 plugins 属性中传入插件实例来应用插件:

plugins: [new webpack.DefinePlugin({ // 内置的插件(DefinePlugin 容许创立可在编译时配置的全局常量)
      // Definitions...
    })
    new HtmlWebpackPlugin({template: './src/index.html'}) // 第三方插件,须要装置
]

应用 Plugin 的难点在于把握 Plugin 自身提供的配置项,而不是在 Webpack 中应用 Plugin。webpack 领有相当多的插件,罕用的插件都能够在官网文档 -plugins 上查找到,文档中也有相干配置的阐明和案例,也算比拟敌对了。

其余罕用配置

resolve

resolve 用于配置模块解析规定,能够通过此配置更改 webpack 默认的解析规定。

webpack 的模块门路解析规定和 Node.js 的模块机制一样。

  • 如果是相对路径

    1. 查找以后模块的目录下是否有对应文件 / 夹
    2. 如果是文件则间接加载
    3. 如果是文件夹,则持续查找文件夹下的 package.json 文件
    4. 如果有 package.json 文件,则依照其中 main 属性申明失去的文件名来查找文件
    5. 如果无 package.json 或者无 main 属性,则查找 index.js 文件
  • 如果间接是模块名
    会顺次查找当前目录下、父目录、父父目录、… 始终到根目录,直到找到目录下的 node_modules 文件夹,查找是否有对应的模块
  • 如果是绝对路径
    间接查找此门路对应的文件
  • resolve.alias:用于定义一些门路简写占位符,也有人称之为门路别名映射,目标是简化模块导入时的门路。
module.exports = {
  resolve: {
    alias: {@: path.resolve(__dirname, 'src/') // 这里就将 @映射到了 /src/ 目录了,在应用时用 @就行
    }
  }
};
  • resolve.extensions:解析文件时,缺省文件扩展名时,尝试主动补全的扩展名集,尝试的程序从前到后,个别将高频应用的扩展名放在后面,优先匹配。
module.exports = {
  resolve: {extensions: ['.wasm', '.mjs', '.js', '.json']
  }
};
  • resolve.enforceExtension:如果是 true,将不容许加载无扩展名 (extension-less) 文件,也即是不会主动进行扩展名补全。
  • resolve.modules:webpack 解析模块时应该搜寻的目录,默认为node_modules,如果我的项目中某个文件夹频繁应用,就能够增加进此配置,引入文件时门路就能够省略该目录。
module.exports = {
  resolve: {modules: ['node_modules']
  }
};

optimization

webpack4.0 将一些优化的配置都放在了该属性下,依据 mode 来进行不同的优化,也能够进行手动配置和重写。

  • optimization.minimize:是否应用 TerserPlugin 压缩代码,production 模式下,默认是 true
module.exports = {
  optimization: {minimize: false}
};
  • optimization.minimizer:容许通过提供一个或多个定制过的 TerserPlugin 实例,笼罩默认压缩工具(minimizer)。
module.exports = {
  optimization: {
    minimizer: [
      new TerserPlugin({
        cache: true,
        parallel: true,
        sourceMap: true, // Must be set to true if using source-maps in production
        terserOptions: {
          // https://github.com/webpack-contrib/terser-webpack-plugin#terseroptions
          compress:{
            drop_console: true, // 去除 consle
            drop_debugger: true  // 去除 debugger
          }
        }
      }),
    ],
  }
};

devServer

在我的项目开发时,如果不能实时看到开发的预览成果,是不是心里没底?所以咱们须要一个工具,来启动一个 server,让咱们在开发的时候能够实时预览,webpack-dev-server就是这样一个工具,它会基于 express 启动一个 server,提供一些好用的性能:

  • 主动关上浏览器
  • 文件监听
  • 主动刷新与模块热替换
  • 跨域代理

这是一个十分实用的工具,用了就会上瘾系列。webpack-dev-server并没有被 webpack 内置,须要咱们自行装置(npm i -D webpack-dev-server),它所有的配置都在配置文件的 devServer 属性中。

  • devServer.before,devServer.after:其实相当于 devServer 的中间件,提供执行自定义处理程序的性能,实质是一个函数,接管 devServer 实例作为参数。

    例如,能够在 before 中咱们能够来启动一个 server 用来做数据 mock。

// webpack.config.js
module.exports = {
  devServer: {before: function(app, server) {app.get('/some/path', function(req, res) {res.json({ custom: 'response'});
      });
    }
  }
};
  • devServer.hostdevServer.port:别离用来配置启动 server 的主机地址和端口。
module.exports = {
  //...
  devServer: {
    host: '0.0.0.0',
    port: 8080
  }
};
  • devServer.hot:开启模块热替换 HMR(Hot Module Replacement)性能,开启后它会尽量采取不刷新整个页面的形式来部分热更新页面。

留神:必须有 webpack.HotModuleReplacementPlugin 能力齐全启用 HMR。如果 webpack-dev-server 是通过 webpack-dev-server --hot 选项启动的,那么这个插件会被主动增加,否则须要把它手动增加到 webpack.config.js的 plugins 中。

module.exports = {
    //...
    devServer: {
        hot: true, // 开启模块热替换
        // ...
    },
    plugins: [new webpack.HotModuleReplacementPlugin() // 须要增加模块热替换插件,如果启动带上了 --hot 参数则不须要手动增加此插件
    ]
}

开启热替换后,须要编写控制代码来响应更新时的操作:

if (module.hot) { // 先判断是否开启热替换
  module.hot.accept('./library.js', function() { // library.js 更新将会触发此处的回调函数
    // 应用更新过的 library 模块执行某些操作...
  });
}

看起来,本人来写这些控制代码还是比拟麻烦,不过侥幸的是,很多 loader(如 style-loader、vue-loader)外部都实现了热替换,而不必咱们本人编写。

  • devServer.inline:举荐设置为 true,实时预览重载的脚本将以内联模式插入到包中,设置为 false 将应用 iframe 模式,采纳轮询的形式执行实时重载。
  • devServer.open:配置是否主动关上浏览器。
  • devServer.overlay:当呈现编译器谬误或正告时,在浏览器中显示覆盖层提醒,默认 false。

  • devServer.proxy:在前后端接口联调的过程中,跨域是一个十分常见的问题,要是后端大哥装大爷的话,那工作就很难做上来了。跨域究其原因是受浏览器的同源策略限度,而服务端则不会有此限度。所以咱们能够通过 nodeServer 将后端的接口服务代理到本地,在申请的时候间接拜访本地 nodeServer 地址,而后 nodeServer 再将申请转发到指标服务端,拿到后果后返回给本地的申请。

    proxy 就是用来做这个事件的,它是基于弱小的 http-proxy-middleware 来实现的,配置也是雷同的。

module.exports = {
  //...
  devServer: {
    proxy: {
      '/api': {
        target: 'http://your-host.com', // 代理的指标地址,/api 的申请都会被代理到 http://your-host.com/api
        secure: false, // 如果应用了 HTTPS,须要敞开此配置
        pathRewrite: {'^/api': '' // 重写, 指标地址中是否蕴含 /api, 如此设置 /api 的申请都会被代理到 http://your-host.com},
        bypass: function(req, res, proxyOptions) { // 如果想本人管制代理,能够应用此配置来绕过代理
          if (req.headers.accept.indexOf('html') !== -1) {console.log('Skipping proxy for browser request.');
            return '/index.html';
          }
        }
      }
    }
  }
};
  • devServer.publicPath:资源的拜访门路。
module.exports = {
  //...
  devServer: {publicPath: '/assets/' // 能够通过 http://localhost:8080/assets/* 拜访到 assets 目录下的资源}
};

devtool

此选项管制是否生成以及如何生成 source map,不同的值会显著影响到构建 (build) 和从新构建 (rebuild) 的速度。

devtool 构建速度 从新构建速度 生产环境 品质(quality)
(none) +++ +++ yes 打包后的代码
eval +++ +++ no 生成后的代码
cheap-eval-source-map + ++ no 转换过的代码(仅限行)
cheap-module-eval-source-map o ++ no 原始源代码(仅限行)
eval-source-map + no 原始源代码
cheap-source-map + o yes 转换过的代码(仅限行)
cheap-module-source-map o yes 原始源代码(仅限行)
inline-cheap-source-map + o no 转换过的代码(仅限行)
inline-cheap-module-source-map o no 原始源代码(仅限行)
source-map yes 原始源代码
inline-source-map no 原始源代码
hidden-source-map yes 原始源代码
nosources-source-map yes 无源代码内容

+++ 十分疾速, ++ 疾速, + 比拟快, o 中等, - 比较慢, --

个别在 生产环境举荐应用 none(不生成)或者source-map(生成独自的一个文件)选项,而在开发环境能够从evaleval-source-mapcheap-eval-source-mapcheap-module-eval-source-map 中抉择一个。

webpack 仓库中蕴含一个 显示所有 devtool 变体成果的示例。这些例子或者会有助于你了解这些差别之处。 看似值比拟多,只有在对应的环境中依据需要(均衡构建速度和打包成绩品质)配置适合的值就好。

externals

用来排除特定的依赖,排除的依赖将不会打包进成绩中,而是在程序运行时再去内部获取。

例如,在开发一个 library 的时候,如果有依赖其余的库,在打包库的时候就须要排除依赖的库,而不应该把依赖的库打包到咱们的library 外面。

另外,在日常的开发中,也能够利用此配置来实现资源以 CDN 形式引入:

<!-- index.html -->
<script
  src="https://code.jquery.com/jquery-3.1.0.js"
  integrity="sha256-slogkvB1K3VOkzAI8QITxV3VzpOnkeNVsKvtkYLMjfk="
  crossorigin="anonymous">
</script>
// webpack.config.js
module.exports = {
  //...
  externals: {jquery: 'jQuery'}
};
// src/index.js
import $ from 'jquery'; // 此处的导入能够失常运行,然而打包的时候不会蕴含,运行的时候会去检索 jquery 全局变量
$('.my-element').animate(/* ... */);

cache

配置是否缓存生成的 webpack 模块和 chunk,能够用来改善构建速度。

下面就是一些罕用的配置,把握了这些,就能够在我的项目中本人来配置一套打包流程了。

实际一下

1. 目录布局

整个我的项目的目录构造,咱们做如下布局:

- build                           // webpack 配置文件目录
   |- webpack.base.conf.js
   |- webpack.dev.conf.js
   |- webpack.prod.conf.js
- dist                            // 打包输入目录
- public                        // 搁置不须要解决的动态文件和 HTML 模板
   |- js
   |- index.html
- src                            // 我的项目外围代码,与业务相干的都能够放在此处
   |- assets                    
   |- index.js                    // 入口 entry
   |- ...
   

用过 vuecli2.0 的同学应该会很相熟这个构造。

2. 根底配置

尽管,webpack 默认提供了两种 Mode 及内置了相应的优化,可能满足一些简略我的项目,然而一些简单的我的项目远远不止这两套环境(还有测试、预公布等环境),每个环境中的配置也有着微小差别,遵循逻辑拆散准则,此时能够依据环境将配置文件拆分为独立的文件。

上面以只思考两种环境为例:

实现性能 开发环境(速度优先) 生成环境(性能优先)
代码压缩
图片压缩
css 文件抽离
模块热替换
devserver、proxy
source-map eval-cheap-source-map none 或者 source-map

除了上述列举内容,理论状况可能还有更多的差异性,总的来看,个别就是开发环境更加偏重构建速度,生产环境更加偏重代码的执行性能。

咱们能够将配置拆分成三个文件:

  • webpack.base.conf.js:开发环境和生产环境专用的配置,最终应用 webpack-merge合并到 dev 和 prod 两个配置中。
// public/webpack.base.conf.js
const path = require('path');
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
   entry: '../src/index.js',
   output: {filename: '[name].[hash:8].js',
     path: path.resolve(__dirname, '../dist')
   },
   plugins: [new CleanWebpackPlugin(), // 打包前清空输入目录
     new HtmlWebpackPlugin({
           title: 'webpack',
          template: './public/index.html',
          filename: 'index.html'
     })
   ]
};
  • webpack.dev.conf.js:开发环境特有的配置,一些辅助开发的配置都放到此文件。
// public/webpack.dev.conf.js
const {merge} = require('webpack-merge');
const baseConfig = require('./webpack.base.conf.js');

module.exports = merge(baseConfig, {
   mode: 'development',
   devtool: 'inline-source-map',
   devServer: {
    contentBase: '../dist',
    hot: true,
    // ...
   },
   // ..
});
  • webpack.prod.conf.js:生产环境特有的配置,一些针对输入文件体积品质优化的都放到此文件。
// public/webpack.prod.conf.js 
const {merge} = require('webpack-merge');
const baseConfig = require('./webpack.base.conf.js');

 module.exports = merge(baseConfig, {
   mode: 'production',
   // ...
 });

而后在 npm script 中配置对应的命令,来指定不同的配置文件:

// package.json
"scripts": {
    "start": "webpack-dev-server --config ./build/webpack.dev.conf.js",
    "build": "webpack --config ./build/webpack.prod.conf.js"
 }

3. 功能完善

应用 HTML 模板

前端我的项目个别都会有一个入口(index.html),须要在此文件中引入打包后的资源,然而每次打包后手动将资源引入太麻烦,特地是输入文件的名字应用了占位符时(每次打包输入文件的名称都会不一样),几乎就是搞事件嘛。此时,咱们就能够通过 html-webpack-plugin 来主动引入打包后的资源。

npm install html-webpack-plugin -D  
// public/webpack.base.conf.js
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
  // ...
  plugins: [
    new HtmlWebpackPlugin({
        title:'利用名称',
            template: './public/index.html', // 配置应用的文件模板,如果不配置,将会应用默认的内置模板
        ... // 其余配置按需食用
    }),
  ],
}

留神:如果须要打包多页利用,仅需实例化多个html-webpack-plugin,在每个实例中配置相应的 chunk 即可。

// public/webpack.base.conf.js
module.exports = {
    entry:{
      app1:'../src/app1.js',  
      app2:'../src/app2.js' 
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: './public/app1.html', // 模板
            filename: 'app1.html', // 打包输入文件名
            chunks: ['app1'] // 对应的 chunk,和 entry 中定义的 chunk 对应
        }),
        new HtmlWebpackPlugin({
            template: './public/app2.html', // 模板
            filename: 'app2.html', // 打包输入文件名
            chunks: ['app2'] // 对应的 chunk,和 entry 中定义的 chunk 对应
        }),
    ]
}

主动清理输入文件

因为应用了占位符,每次输入的文件可能不一样,那么就须要在每次打包前革除一下上次输入的文件,clean-webpack-plugin 就能够帮咱们主动实现这件事。

npm install clean-webpack-plugin -D
// public/webpack.base.conf.js
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
module.exports = {
    plugins: [new CleanWebpackPlugin() // 会主动革除输入文件夹
    ]
}

动态资源拷贝

有些资源是不须要 webpack 来进行编译的,如 VueCli4.0 中 public 中的资源文件,只须要将其拷贝到指标文件就能够了。CopyWebpackPlugin 能够把指定文件或目录拷贝到构建的输入目录中。

npm install copy-webpack-plugin -D
// public/webpack.base.conf.js
const CopyWebpackPlugin = require('copy-webpack-plugin');

module.exports = {
    plugins: [
        new CopyWebpackPlugin({
          patterns: [
            // 将 public/js/ 下的所有 js 文件拷贝到 dist/js 目录中
            { 
              from: './public/js/*.js', 
              to: 'js', 
              flatten: true  // 拷贝是否不带门路,为 true 只会拷贝文件,而不会携带文件门路
            }, 
            // ... 多个须要拷贝的文件 / 夹在此持续增加
          ]
        })
    ]
}

应用 ES Next 语法

ES 标准越来越欠缺,ES6 给咱们带来了很多实用的新个性,可能大大晋升开发体验,但浏览器的反对总是那么不尽人意。而 Babel 的呈现,让咱们能够在开发环境应用更时尚的语法(jsx 也是能够的),而后生产环境将代码转换成浏览器反对的语法。对于 Babel 的具体介绍会放在后续内容中,这里不再赘述。

npm install babel-loader -D
npm install @babel/core @babel/preset-env @babel/plugin-transform-runtime -D
npm install @babel/runtime @babel/runtime-corejs3
// public/webpack.base.conf.js
module.exports = {
    module: {
        rules: [
            {
                test: /\.jsx?$/,
                use: ['babel-loader'],
                exclude: /node_modules/ // 排除 node_modules 目录
            }
        ]
    }
}

在根目录创立一个 babel 配置文件babel.config.js

 // babel.config.js 
module.exports = {"presets": ["@babel/preset-env"],
    "plugins": [
        [
            "@babel/plugin-transform-runtime",
            {"corejs": 3}
        ]
    ]
}

注入全局变量

在应用一些框架的时候,须要在很多文件都引入一下,比方在应用 React 的时候,须要在每个文件都引入一下,否则会报错。这时,如果想偷个懒,利用 ProvidePlugin 来主动注入也是能够的。

// public/webpack.base.conf.js
const webpack = require('webpack');

module.exports = {
    plugins: [
        new webpack.ProvidePlugin({
            React: 'react',
            Component: ['react', 'Component']
        })
    ]
}

如此,在写 React 组件时,就不再须要 import reactcomponent了。

留神:

  • 这玩意虽好,可千万不要贪杯哟,过多的全局变量会出事的。
  • 在开启 ESLint 时,还须要在 global 中做对应配置,否则会报错提醒对应模块未定义。

款式类文件解决

款式类文件解决次要蕴含款式文件引入、浏览器兼容性语法的主动补全及预处理器编译三局部内容。

css 解析和引入

如果仅应用 css 来做为款式文件的话,配置绝对比较简单,只须要借助 css-loader 让 webpack 能够解析 css 文件即可。

npm install css-loader -D
// public/webpack.base.conf.js
module.exports = {
    //...
    module: {
        rules: [
            {
                test: /\.css$/,
                use: 'css-loader',
                exclude: /node_modules/
            }
        ]
    }
}

当初,webpack 能够打包 css 文件了,然而款式并不会失效,因为它们基本没有插入到页面中,须要借助 style-loader 来做款式引入,它会在 head 中动态创建 style 标签,并将 css 插入到其中。

npm install style-loader -D
// public/webpack.base.conf.js
module.exports = {
    //...
    module: {
        rules: [
            {
                test: /\.css$/,
                use: ['style-loader', 'css-loader'], // 留神程序,从后到前
                exclude: /node_modules/
            }
        ]
    }
}
主动补全兼容性前缀

因为当初浏览器对某些 css 个性反对还不够欠缺,在应用这些新个性的时候,往往须要加上一些浏览器特定的前缀,也是一个比拟麻烦的事件,这时候就轮到 postcss 上场了。

npm install postcss-loader postcss autoprefixer -D
// public/webpack.base.conf.js
module.exports = {
    //...
    module: {
        rules: [
            {
                test: /\.css$/,
                use: ['style-loader', 'css-loader', {
                    loader: 'postcss-loader',
                    options: {plugins: function () {
                            return [require('autoprefixer')({
                                    "overrideBrowserslist": [
                                        ">0.25%",
                                        "not dead"
                                    ]
                                })
                            ]
                        }
                    }
                }],
                exclude: /node_modules/
            }
        ]
    }
}

看到这个俊俏简短的配置,总感觉怪怪的,个别会将它们抽离到 postcssbrowserslist的配置文件中。

在我的项目根目录创立 postcss 配置文件postcss.config.js

// postcss.config.js
module.exports = {
  plugins: {'autoprefixer': {}
  }
};

另外再创立一个 browserslist 配置文件.browserslistrc,用来指定要兼容的浏览器。

> 1%
last 2 versions
not ie <= 8

当初配置文件看起就要难受多了。

// public/webpack.base.conf.js
module.exports = {
    //...
    module: {
        rules: [
            {
                test: /\.css$/,
                use: ['style-loader', 'css-loader', 'postcss-loader'],
                exclude: /node_modules/
            }
        ]
    }
}
应用预处理器

目前前端比拟风行的三种 css 预处理器都有相应的工具进行解决,应用办法也是相似的,装置相应的 loader 和外围处理程序就能够了。

预处理器 loader 外围处理程序
less less-loader less
sass sass-loader node-sass 或 dart-sass
stylus stylus-loader stylus

以 less 为例:

npm install less-loader less -D
// public/webpack.base.conf.js
module.exports = {
    //...
    module: {
        rules: [
            {
                test: /\.less$/,
                use: ['style-loader', 'css-loader','postcss-loader','less-loader'],
                exclude: /node_modules/
            }
        ]
    }
}
款式文件拆散

通过如上几个 loader 解决,css 最终是打包在 js 中的,运行时会动静插入 head 中,然而咱们个别在生产环境会把 css 文件分离出来(有利于用户端缓存、并行加载及减小 js 包的大小),这时候就用到 mini-css-extract-plugin 插件。

npm i -D mini-css-extract-plugin
// public/webpack.base.conf.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
  module: {
    rules: [
      {
        test: /\.less$/,
        use: [
          // 插件须要参加模块解析,须在此设置此项,不再须要 style-loader          
          {
             loader: MiniCssExtractPlugin.loader, 
             options: {
                hmr: true, // 模块热替换,仅需在开发环境开启
                // reloadAll: true,
                // ... 其余配置
             }
          },
           'css-loader',
           'postcss-loader',
           'less-loader'
        ],
      },
    ],
  },
  plugins: [
    new MiniCssExtractPlugin({filename: '[name].css', // 输入文件的名字
      // ... 其余配置
    }), 
  ]
};

图片 / 字体文件解决

url-loaderfile-loader 都能够用来解决本地的资源文件,如图片、字体、音视频等。性能也是相似的,不过url-loader 能够指定在文件大小小于指定的限度时,返回 DataURL,不会输入实在的文件,能够缩小低廉的网络申请。

npm install url-loader file-loader -D
// public/webpack.base.conf.js
module.exports = {
    modules: {
        rules: [
            {test: /\.(png|jpg|gif|jpeg|webp|svg|eot|ttf|woff|woff2)$/,
                use: [
                    {
                        loader: 'url-loader', // 仅配置 url-loader 即可,外部会主动调用 file-loader
                        options: {
                            limit: 10240, // 小于此值的文件会被转换成 DataURL
                            name: '[name]_[hash:6].[ext]', // 设置输入文件的名字
                            outputPath: 'assets', // 设置资源输入的目录
                            esModule: false 
                        }
                    }
                ],
                exclude: /node_modules/
            }
        ]
    }
}

留神:

limit 的设置要设置正当,太大会导致 JS 文件加载变慢,须要兼顾加载速度和网络申请次数。

如果须要应用图片压缩性能,能够应用 image-webpack-loader。

实时预览及模块热替换

在开发环境,能够借助 webpack-dev-server 启动一个 server 来实时预览应用程序。因为 webpack-dev-server 并不蕴含在外围库中,所以须要额定装置。

npm install webpack-dev-server -D
// public/webpack.dev.conf.js
module.exports = {
    //...
    devServer: {
        port: '8080', // 默认是 8080
        hot: true, // 开启模块热替换
        publicPath:'/', // 构建好的动态文件拜访门路,能够和 output.publicPath 保持一致
        inline: true, // 默认开启 inline 模式,如果设置为 false, 开启 iframe 模式
        stats: "errors-only", // 终端仅打印 error
        overlay: true, // 启用浮层提醒
        clientLogLevel: "silent", // 日志等级
        compress: false, // 是否启用 gzip 压缩
        contentBase: path.join(__dirname, "../public") , // 配置额定的动态文件内容的拜访门路
        proxy: { // 申请代理, 解决开发环境跨域问题
            // 依据状况配置
        }
    },
    plugins: [new webpack.HotModuleReplacementPlugin() // 须要增加模块热替换插件
    ]
}

优化调试性能

为代码生成 source-map 有助于调试排错。

// public/webpack.dev.conf.js
module.exports = {devtool: 'cheap-module-eval-source-map' // 开发环境下应用内联形式,疏忽列信息}
// public/webpack.prod.conf.js
module.exports = {devtool: 'none' // 也能够应用 'source-map'}

除了上述配置项,咱们个别在开发环境还会开启 ESLint,因为前面有一篇专门的内容来叙述,所以此处不赘述。

到此,咱们就可能本人进行打包配置了,日常开发应该是能够满足了。

结语

其实 webpack 配置很多,置信没有一个人可能记住如此之多的 api,这也是困扰初学者的一个问题。这里和大家分享一下我的学习办法:

  1. 首先,理解 webpack 是解决什么问题及有哪些能力,而不是一开始就去记忆那些干燥的配置,成为所谓的 webpack 配置工程师。
  2. 其次,依照性能和用处将 api 分类,不便记忆和后续的查阅。特地是 loader 和 plugin,数量泛滥,每个的配置也不一样,咱们只须要理解解决特定类型的文件和操作该用哪个就行,不用记住每个的配置。
  3. 最初,在须要用到某个性能的时候,依据上一步的分类,再去查阅对应的文档。官网也提供了残缺的配置,能够很不便的查找对应的配置文档。

相比晚期的文档,目前官方网站上的文档品质已比较完善和敌对了,自行修炼齐全没有问题了(找个妹子双修成果更好哦)。

好了,对于 webpack 的根底局部就告一段落了,在接下来咱们将会着重介绍一些性能优化及原理方面的货色,下篇再见吧!

参考文档:webpack 官网 - 中文,深入浅出 webpack

正文完
 0