webpack4.0 入门实践教程


Posted in Javascript onOctober 08, 2018

webpack 可以看做是模块打包机:他做的事情是,分析你的项目结构,找到 JavaScript 模块以及其他的一些浏览器不能直接运行的扩展语言( ScssTypeScript 等),将其打包为合适的格式以供浏览器使用

构建就是把源代码转换成发布到线上可执行的 JavaScript 、CSS、HTML 代码,包括以下内容:

  • 代码转换TypeScript 编译成 JavaScriptSCSS 编译成 CSS 等等 文件优化 :压缩 JavaScript 、CSS、HTML 代码,压缩合并图片等
  • 代码分割 :提取多个页面的公共代码、提取首屏不需要执行部分的代码让其异步加载
  • 模块合并 :在采用模块化的项目有很多模块和文件,需要构建功能把模块分类合并成一个文件
  • 自动刷新 :监听本地源代码的变化,自动构建,刷新浏览器
  • 代码校验 :在代码被提交到仓库前需要检测代码是否符合规范,以及单元测试是否通过
  • 自动发布 :更新完代码后,自动构建出线上发布代码并传输给发布系统。

构建其实是工程化、自动化思想在前端开发中的体现。把一系列流程用代码去实现,让代码自动化地执行这一系列复杂的流程。

webpack 的基本概念

新建 webpack.config.js 文件

要想对 webpack 中增加更多的配置信息,我们需要建立一个 webpack 的配置文件。在根目录下创建 webpack.config.js 后再执行 webpack 命令,webpack 就会使用这个配置文件的配置了

配置中具备以下的基本信息:

module.exports = {
 entry: '', // 打包入口:指示 webpack 应该使用哪个模块,来作为构建其内部依赖图的开始
 output: '', // 出口
 resolve: {}, // 配置解析:配置别名、extensions 自动解析确定的扩展等等
 devServer: {}, // 开发服务器:run dev/start 的配置,如端口、proxy等
 module: {}, // 模块配置:配置loader(处理非 JavaScript 文件,比如 less、sass、jsx、图片等等)等
 plugins: [] // 插件的配置:打包优化、资源管理和注入环境变量
}

配置打包入口和出口

首先我们往 webpack.config.js 添加点配置信息

const path = require('path')

module.exports = {
 // 指定打包入口
 entry: './src/index.js',

 // 打包出口
 output: {
 path: path.resolve(__dirname, 'dist'), // 解析路径为 ./dist
 filename: 'bundle.js'
 }
}

上面我们定义了打包入口 ./src/index.js ,打包出口为 ./dist , 打包的文件夹名字为 bundle.js ,执行 npm run build 命令后,index.js 文件会被打包为 bundle.js 文件。此时随便建立一个 html 文件引用这个 bundle.js 就可以看到你在 index.js 写的代码了。

配置 babel

babel-loader

Babel 是一个让我们能够使用 ES 新特性的 JS 编译工具,我们可以在 webpack 中配置 Babel,以便使用 ES6、ES7 标准来编写 JS 代码。

Babel 7 的相关依赖包需要加上 @babel scope。一个主要变化是 presets 设置由原来的 env 换成了 @babel/preset-env , 可以配置 targets , useBuiltIns 等选项用于编译出兼容目标环境的代码。其中 useBuiltIns 如果设为 "usage" ,Babel 会根据实际代码中使用的 ES6/ES7 代码,以及与你指定的 targets,按需引入对应的 polyfill ,而无需在代码中直接引入 import '@babel/polyfill' ,避免输出的包过大,同时又可以放心使用各种新语法特性。

npm i babel-loader @babel/core @babel/preset-env -D

笔者这里配的版本号如下

{
 "babel-loader": "^8.0.4",
 "@babel/core": "^7.1.2",
 "@babel/preset-env": "^7.1.0"
}

babel-loader: 用 babel 转换 ES6 代码需要使用到 babel-loader

@babel-preset-env: 默认情况下是等于 ES2015 + ES2016 + ES2017,也就是说它对这三个版本的 ES 语法进行转化。

 @babel/core:babel 核心库

根目录下新建 .babelrc 文件

{
 "presets": [
 [
 "@babel/preset-env",
 {
 "modules": false,
 "targets": {
 "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
 },
 "useBuiltIns": "usage"
 }
 ]
 ]
}
  • presets 是一堆 plugins 的预设,起到方便的作用。
  • plugins 是编码转化工具,babel 会根据你配置的插件对代码进行相应的转化。

修改 webpack.config.js

module.exports = {
 module: {
 rules: [
 //...
 {
 test: /\.m?js$/,
 exclude: /(node_modules|bower_components)/,
 use: {
 loader: 'babel-loader'
 }
 }
 ]
 }
}

babel/polyfill 和 transform-runtime

Babel 默认只转换新的 JavaScript 句法(syntax),而不转换新的 API ,比如 Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise 等全局对象,以及一些定义在全局对象上的方法(比如 Object.assign)都不会转码。

babel-polyfill: 如上述所说,对于新的 API,你可能需要引入 babel-polyfill 来进行兼容

关键点

  • babel-polyfill 是为了模拟一个完整的 ES2015+环境,旨在用于应用程序而不是库/工具。
  • babel-polyfill 会污染全局作用域

babel-runtime 的作用:

  • 提取辅助函数 。ES6 转码时,babel 会需要一些辅助函数,例如 _extend。babel 默认会将这些辅助函数内联到每一个 js 文件里, babel 提供了 transform-runtime 来将这些辅助函数“搬”到一个单独的模块 babel-runtime 中,这样做能减小项目文件的大小。
  • 提供 polyfill :不会污染全局作用域,但是不支持实例方法如 Array.includes

babel-runtime 更像是分散的 polyfill 模块,需要在各自的模块里单独引入,借助 transform-runtime 插件来自动化处理这一切,也就是说你不要在文件开头 import 相关的 polyfill ,你只需使用, transform-runtime 会帮你引入。

对于开发应用来说,直接使用上述的按需 polyfill 方案是比较方便的,但如果是开发工具、库的话,这种方案未必适合( babel-polyfill 是通过向全局对象和内置对象的 prototype 上添加方法实现的,会造成全局变量污染)。Babel 提供了另外一种方案 transform-runtime ,它在编译过程中只是将需要 polyfill 的代码引入了一个指向 core-js 中对应模块的链接(alias)。关于这两个方案的具体差异和选择,可以自行搜索相关教程,这里不再展开,下面提供一个 transform-runtime 的参考配置方案。

首先安装 runtime 相关依赖

npm i @babel/plugin-transform-runtime -D
npm i @babel/runtime -S

修改 .babelrc

{
 //...
 "plugins": ["@babel/plugin-transform-runtime"]
}

打包前清理源目录文件 clean-webpack-plugin

每次打包,都会生成项目的静态资源,随着某些文件的增删,我们的 dist 目录下可能产生一些不再使用的静态资源,webpack 并不会自动判断哪些是需要的资源,为了不让这些旧文件也部署到生产环境上占用空间,所以在 webpack 打包前最好能清理 dist 目录。

npm install clean-webpack-plugin -D

修改 webpack.config.js 文件

const CleanWebpackPlugin = require('clean-webpack-plugin')
module.exports = {
 plugins: [new CleanWebpackPlugin(['dist'])]
}

提取公用代码

假如你 a.jsb.js 都 import 了 c.js 文件,这段代码就冗杂了。为什么要提取公共代码,简单来说,就是减少代码冗余,提高加载速度。

module.exports = {
 //...
 optimization: {
 splitChunks: {
 cacheGroups: {
 commons: {
 // 抽离自己写的公共代码
 chunks: 'initial',
 name: 'common', // 打包后的文件名,任意命名
 minChunks: 2, //最小引用2次
 minSize: 0 // 只要超出0字节就生成一个新包
 },
 styles: {
 name: 'styles', // 抽离公用样式
 test: /\.css$/,
 chunks: 'all',
 minChunks: 2,
 enforce: true
 },
 vendor: {
 // 抽离第三方插件
 test: /node_modules/, // 指定是node_modules下的第三方包
 chunks: 'initial',
 name: 'vendor', // 打包后的文件名,任意命名
 // 设置优先级,防止和自定义的公共代码提取时被覆盖,不进行打包
 priority: 10
 }
 }
 }
 }
}

hash

hash 是干嘛用的? 我们每次打包出来的结果可能都是同一个文件,那我上线的时候是不是要替换掉上线的 js,那我怎么知道哪是最新的呢,我们一般会清一下缓存。而 hash 就是为了解决这个问题而存在的

我们此时在改一些 webpack.config.js 的配置

module.exports = {
 //...
 output: {
 path: path.resolve(__dirname, 'dist'),
 filename: '[name].[hash:8].js'
 },
 //...
 plugins: [
 new MiniCssExtractPlugin({
 filename: '[name].[hash:8].css',
 chunkFilename: '[id].[hash:8].css'
 })
 ]
}

减少 resolve 的解析,配置别名

如果我们可以精简 resolve 配置,让 webpack 在查询模块路径时尽可能快速地定位到需要的模块,不做额外的查询工作,那么 webpack 的构建速度也会快一些

module.exports = {
 resolve: {
 /**
 * alias: 别名的配置
 *
 * extensions: 自动解析确定的扩展,
 * 比如 import 'xxx/theme.css' 可以在extensions 中添加 '.css', 引入方式则为 import 'xxx/theme'
 * @default ['.wasm', '.mjs', '.js', '.json']
 *
 * modules 告诉 webpack 解析模块时应该搜索的目录
 * 如果你想要添加一个目录到模块搜索目录,此目录优先于 node_modules/ 搜索
 * 这样配置在某种程度上可以简化模块的查找,提升构建速度 @default node_modules 优先
 */
 alias: {
 '@': path.resolve(__dirname, 'src'),
 tool$: path.resolve(__dirname, 'src/utils/tool.js') // 给定对象的键后的末尾添加 $,以表示精准匹配
 },
 extensions: ['.wasm', '.mjs', '.js', '.json', '.jsx'],
 modules: [path.resolve(__dirname, 'src'), 'node_modules']
 }
}

webpack-dev-serve

上面讲到了都是如何打包文件,但是开发中我们需要一个本地服务,这时我们可以使用 webpack-dev-server 在本地开启一个简单的静态服务来进行开发。

webpack-dev-server 是 webpack 官方提供的一个工具,可以基于当前的 webpack 构建配置快速启动一个静态服务。当 modedevelopment 时,会具备 hot reload 的功能,即当源码文件变化时,会即时更新当前页面,以便你看到最新的效果。...

npm install webpack-dev-server -D

package.json 中 scripts 中添加

"start": "webpack-dev-server --mode development"

默认开启一个本地服务的窗口 http://localhost:8080/ 便于开发

配置开发服务器

我们可以对 webpack-dev-server 做针对性的配置

module.exports = {
 // 配置开发服务器
 devServer: {
 port: 1234,
 open: true, // 自动打开浏览器
 compress: true // 服务器压缩
 //... proxy、hot
 }
}
  • contentBase: 服务器访问的根目录(可用于访问静态资源)
  • port: 端口
  • open: 自动打开浏览器

 模块热替换(hot module replacement)

模块热替换( HMR - Hot Module Replacement )功能会在应用程序运行过程中替换、添加或删除模块,而无需重新加载整个页面。主要是通过以下几种方式,来显著加快开发速度:

  • 保留在完全重新加载页面时丢失的应用程序状态。
  • 只更新变更内容,以节省宝贵的开发时间。
  • 调整样式更加快速 - 几乎相当于在浏览器调试器中更改样式。

上面我们 npm start 后修改一次文件,页面就会刷新一次。这样就存在很大问题了,比如我们使用 redux , vuex 等插件,页面一刷新那么存放在 redux , vuex 中的东西就会丢失,非常不利于我们的开发。

HMR 配合 webpack-dev-server ,首先我们配置下 webpack.config.js

const webpack = require('webpack')

module.exports = {
 devServer: {
 //...
 hot: true
 },
 plugins: [
 new webpack.HotModuleReplacementPlugin()
 //...
 ]
}

配置后还不行,因为 webpack 还不知道你要更新哪里, 修改 src/index.js 文件, 添加

if (module.hot) {
 module.hot.accept()
}

重启服务, npm start 之后,修改引入 index.js 文件后,页面就不会重新刷新了,这便实现了 HMR

但是但是有个问题是,你修改 css/less 等样式文件并未发生改变, what ?

HMR 修改样式表 需要借助于 style-loader , 而我们之前用的是 MiniCssExtractPlugin.loader , 这也好办,修改其中一个 rules 就可以了,我们可以试试改

module.exports = {
 module: {
 rules: [
 {
 test: /\.less$/,
 use: [
 // MiniCssExtractPlugin.loader,
 'style-loader',
 'css-loader',
 {
 loader: 'postcss-loader',
 options: {
 plugins: [require('autoprefixer')] // 添加css中的浏览器前缀
 }
 },
 'less-loader'
 ]
 }
 ]
 }
}

这样我们修改 less 文件就会发现 HMR 已经实现了。

其实,我们可以发现,dev 下配置的 loader 为 style-loader , 而生产环境下则是需要 MiniCssExtractPlugin.loader

这就涉及到了不同环境之间的配置。可以通过 process.env.NODE_ENV 获取当前是开发环境或者是生产环境,然后配置不同的 loader,这里就不做展开了。下一篇文章打算在做一个 react-cli 或者 vue-cli 的配置,将开发环境的配置与生产环境的配置分开为不同的文件。

结语

前面讲到的知识都是 webpack 的一些基础的知识,更多的资料可以查询webpack 中文官网,官网讲的比较详细,我这里也是讲最常的配置,也是一篇入门系列的文章,文中涉及的知识点还有很多地方还需要完善,譬如 优化 webpack 的构建速度, 减小打包的体积等等。

学习 webpack 4.0 还需要多实践,多瞎搞,笔者也是刚刚学习 webpack 的配置,不对之处请各位指出。

下一篇文章打算从零配置一个脚手架,以加深自己对 webpack 的理解。

本文产生的代码: webpack-dev

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
枚举JavaScript对象的函数
Dec 22 Javascript
javascript获取url上某个参数的方法
Nov 08 Javascript
判断日期是否能跨月查询的js代码
Jul 25 Javascript
9款2014最热门jQuery实用特效推荐
Dec 07 Javascript
jQuery加密密码到cookie的实现代码
Apr 18 jQuery
js获取元素的偏移量offset简单方法(必看)
Jul 05 Javascript
jQuery取得元素标签名称小结(附代码)
Aug 16 jQuery
vue-router中的hash和history两种模式的区别
Jul 17 Javascript
浅析Vue 和微信小程序的区别、比较
Aug 03 Javascript
聊聊Vue 中 title 的动态修改问题
Jun 11 Javascript
react用Redux中央仓库实现一个todolist
Sep 29 Javascript
在Vue中创建可重用的 Transition的方法
Jun 02 Javascript
vue实现一个炫酷的日历组件
Oct 08 #Javascript
angularJs利用$scope处理升降序的方法
Oct 08 #Javascript
Nuxt升级2.0.0时出现的问题(小结)
Oct 08 #Javascript
vue页面切换过渡transition效果
Oct 08 #Javascript
angularJs自定义过滤器实现手机号信息隐藏的方法
Oct 08 #Javascript
angular中子控制器向父控制器传值的实例
Oct 08 #Javascript
对angularjs框架下controller间的传值方法详解
Oct 08 #Javascript
You might like
PHP写MySQL数据 实现代码
2009/06/15 PHP
PHP实现Unicode编码相互转换的方法示例
2020/11/17 PHP
Laravel框架Auth用户认证操作实例分析
2019/09/29 PHP
用JavaScript脚本实现Web页面信息交互
2006/10/11 Javascript
改善用户体验的五款jQuery插件分享
2011/05/22 Javascript
12款经典的白富美型—jquery图片轮播插件—前端开发必备
2013/01/08 Javascript
一行代码实现纯数据json对象的深度克隆实现思路
2013/01/09 Javascript
js小数运算出现多位小数如何解决
2015/10/08 Javascript
JavaScript统计字符串中每个字符出现次数完整实例
2016/01/28 Javascript
JavaScript给每一个li节点绑定点击事件的实现方法
2016/12/01 Javascript
详解基于Koa2开发微信二维码扫码支付相关流程
2018/05/16 Javascript
Node.js连接Sql Server 2008及数据层封装详解
2018/08/27 Javascript
微信小程序冒泡事件及其阻止方法实例分析
2018/12/06 Javascript
微信小程序中使用Async-await方法异步请求变为同步请求方法
2019/03/28 Javascript
使用taro开发微信小程序遇到的坑总结
2019/04/08 Javascript
JS回调函数 callback的理解与使用案例分析
2019/09/09 Javascript
vue实现直播间点赞飘心效果的示例代码
2019/09/20 Javascript
加速vue组件渲染之性能优化
2020/04/09 Javascript
解决Antd Table组件表头不对齐的问题
2020/10/27 Javascript
如何使用gpu.js改善JavaScript的性能
2020/12/01 Javascript
python基础教程之对象和类的实际运用
2014/08/29 Python
使用Python绘制图表大全总结
2017/02/11 Python
Python实现解析Bit Torrent种子文件内容的方法
2017/08/29 Python
python简单图片操作:打开\显示\保存图像方法介绍
2017/11/23 Python
python django model联合主键的例子
2019/08/06 Python
python中时间转换datetime和pd.to_datetime详析
2019/08/11 Python
python实现简单日志记录库glog的使用
2019/12/13 Python
美国波西米亚风格服装品牌:Show Me Your Mumu
2018/01/05 全球购物
英国女性化妆品收纳和家具网站:Beautify
2019/12/07 全球购物
请编写一个 C 函数,该函数在给定的内存区域搜索给定的字符,并返回该字符所在位置索引值
2014/09/15 面试题
教师年终个人自我评价
2013/10/04 职场文书
世界遗产导游词
2015/02/13 职场文书
《打电话》教学反思
2016/02/22 职场文书
css中z-index: 0和z-index: auto的区别
2021/08/23 HTML / CSS
Python list列表删除元素的4种方法
2021/11/01 Python
Python的三个重要函数详解
2022/01/18 Python