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操作css属性
Dec 30 Javascript
js实现俄罗斯方块小游戏分享
Jan 31 Javascript
js动态控制table的tr、td增加及删除的具体实现
Apr 30 Javascript
js动态获取子复选项并设计全选及提交的实现方法
Jun 24 Javascript
在线引用最新jquery文件的实现方法
Aug 26 Javascript
vue中的非父子间的通讯问题简单的实例代码
Jul 19 Javascript
vue基于mint-ui的城市选择3级联动的示例
Oct 25 Javascript
Vuejs 2.0 子组件访问/调用父组件的方法(示例代码)
Feb 08 Javascript
微信小程序实现两边小中间大的轮播效果的示例代码
Dec 07 Javascript
如何解决js函数防抖、节流出现的问题
Jun 17 Javascript
解决Vue的项目使用Element ui 走马灯无法实现的问题
Aug 03 Javascript
详解三种方式在React中解决绑定this的作用域问题并传参
Aug 18 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文件打包 下载之使用PHP自带的ZipArchive压缩文件并下载打包好的文件
2012/06/13 PHP
smarty高级特性之过滤器的使用方法
2015/12/25 PHP
Linux安装配置php环境的方法
2016/01/14 PHP
yii2中的rules 自定义验证规则详解
2016/04/19 PHP
支付宝支付开发――当面付条码支付和扫码支付实例
2016/11/04 PHP
ext form 表单提交数据的方法小结
2008/08/08 Javascript
jQuery 常见开发使用技巧总结
2009/12/26 Javascript
location对象的属性和方法应用(解析URL)
2013/04/12 Javascript
jQuery控制网页打印指定区域的方法
2015/04/07 Javascript
javascript消除window.close()的提示窗口
2015/05/20 Javascript
JavaScript中实现键值对应的字典与哈希表结构的示例
2016/06/12 Javascript
动态JavaScript所造成一些你不知道的危害
2016/09/25 Javascript
利用jQuery解析获取JSON数据
2017/04/08 jQuery
Vue-resource拦截器判断token失效跳转的实例
2017/10/27 Javascript
详解如何用模块化的方式写vuejs
2017/12/16 Javascript
Element InputNumber 计数器的实现示例
2020/08/03 Javascript
js+canvas实现刮刮奖功能
2020/09/13 Javascript
Javascript柯里化实现原理及作用解析
2020/10/22 Javascript
[02:12]打造更好的电竞完美世界:完美盛典回顾篇
2018/12/19 DOTA
[02:55]含熏伴清风,风行者至宝、屠夫身心及典藏宝瓶二展示
2020/09/08 DOTA
Python如何为图片添加水印
2016/11/25 Python
Python+Appium实现自动化测试的使用步骤
2020/03/24 Python
Python中的__init__作用是什么
2020/06/09 Python
自学python用什么系统好
2020/06/23 Python
英国轻奢珠宝品牌:Astley Clarke
2016/12/18 全球购物
日本民宿预约平台:STAY JAPAN
2017/07/01 全球购物
Seavenger官网:潜水服、浮潜、靴子和袜子
2020/03/05 全球购物
局域网定义和特性
2016/01/23 面试题
如何开发一个JQuery插件
2016/07/28 面试题
小学生打架检讨书
2014/01/26 职场文书
JAVA程序员自荐书
2014/01/30 职场文书
2014年重阳节老干部座谈会上的讲话稿
2014/09/25 职场文书
个人对照检查剖析材料
2014/10/13 职场文书
2014年销售工作总结与计划
2014/12/01 职场文书
2015年试用期工作总结
2014/12/12 职场文书
Python列表删除重复元素与图像相似度判断及删除实例代码
2021/05/07 Python