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表达式:URL 协议介绍
Mar 10 Javascript
JavaScript实现维吉尼亚(Vigenere)密码算法实例
Nov 22 Javascript
jQuery验证插件validation使用指南
Apr 21 Javascript
jQuery实现Tab选项卡切换效果简单演示
Nov 23 Javascript
深入理解jQuery之事件移除
Jun 02 Javascript
使用bootstrapValidator插件进行动态添加表单元素并校验
Sep 28 Javascript
微信小程序登录session的使用
Mar 17 Javascript
浅谈VUE防抖与节流的最佳解决方案(函数式组件)
May 22 Javascript
学习RxJS之JavaScript框架Cycle.js
Jun 17 Javascript
使用layui+ajax实现简单的菜单权限管理及排序的方法
Sep 10 Javascript
在vue中封装方法以及多处引用该方法详解
Aug 14 Javascript
JS前端轻量fabric.js系列之画布初始化
Aug 05 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生成带有雪花背景的验证码
2006/10/09 PHP
PHPwind整合最土系统用户同步登录实现方法
2010/12/08 PHP
如何给phpcms v9增加类似于phpcms 2008中的关键词表
2013/07/01 PHP
实现WordPress主题侧边栏切换功能的PHP脚本详解
2015/12/14 PHP
Zend Framework教程之Loader以及PluginLoader用法详解
2016/03/09 PHP
PHP实现的回溯算法示例
2017/08/15 PHP
彻底搞懂PHP 变量结构体
2017/10/11 PHP
PHP实现可添加水印与生成缩略图的图片处理工具类
2018/01/16 PHP
PHP Post获取不到非表单数据的问题解决办法
2018/02/27 PHP
PHP错误提示It is not safe to rely on the system……的解决方法
2019/03/25 PHP
国外Lightbox v2.03.3 最新版 下载
2007/10/17 Javascript
Javascript的并行运算实现代码
2010/11/19 Javascript
JavaScript var声明变量背后的原理示例解析
2013/10/12 Javascript
JS版的date函数(和PHP的date函数一样)
2014/05/12 Javascript
理解JavaScript的变量的入门教程
2015/07/07 Javascript
老生常谈 js中this的指向
2016/06/30 Javascript
javascript数据结构中栈的应用之符号平衡问题
2017/04/11 Javascript
Jquery-data的三种用法
2017/04/18 jQuery
详解vue-router 2.0 常用基础知识点之router.push()
2017/05/10 Javascript
Vue.js学习记录之在元素与template中使用v-if指令实例
2017/06/27 Javascript
vue页面使用阿里oss上传功能的实例(二)
2017/08/09 Javascript
利用js将ajax获取到的后台数据动态加载至网页中的方法
2018/08/08 Javascript
layui异步加载table表中某一列数据的例子
2019/09/16 Javascript
JavaScript实现鼠标经过表格某行时此行变色
2020/11/20 Javascript
Python文本相似性计算之编辑距离详解
2016/11/28 Python
Python如何读取MySQL数据库表数据
2017/03/11 Python
学点简单的Django之第一个Django程序的实现
2021/02/24 Python
学前教育专业毕业生自荐信
2013/10/03 职场文书
总经理岗位职责
2013/11/09 职场文书
财务出纳员岗位职责
2013/11/26 职场文书
大学四年的个人自我评价
2014/01/14 职场文书
《中彩那天》教学反思
2014/02/22 职场文书
高三毕业典礼主持词
2014/03/27 职场文书
整改落实情况汇报材料
2014/10/29 职场文书
具结保证书
2015/01/17 职场文书
毕业生个人总结
2015/02/28 职场文书