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 相关文章推荐
js的闭包的一个示例说明
Nov 18 Javascript
javascript(jquery)利用函数修改全局变量的代码
Nov 02 Javascript
JQuery魔力之$(&quot;tagName&quot;)与selector
Mar 05 Javascript
提交表单时执行func方法实现代码
Mar 17 Javascript
jquery实现背景墙聚光灯效果示例分享
Mar 02 Javascript
jQuery中DOM树操作之复制元素的方法
Jan 23 Javascript
JavaScript定时器和优化的取消定时器方法
Jul 03 Javascript
AngularJS中的API(接口)简单实现
Jul 28 Javascript
js实现彩色条纹滚动条效果
Mar 15 Javascript
angularjs的select使用及默认选中设置
Apr 08 Javascript
微信小程序 新建登录页并实现tabBar隐藏
Jun 13 Javascript
vue中可编辑树状表格的实现代码
Oct 31 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
MySQL修改密码方法总结
2008/03/25 PHP
php过滤HTML标签、属性等正则表达式汇总
2014/09/22 PHP
jQuery+PHP发布的内容进行无刷新分页(Fckeditor)
2015/10/22 PHP
php实现分页功能的详细实例方法
2019/09/29 PHP
js window.onload 加载多个函数的方法
2009/11/02 Javascript
js动态添加事件并可传参数示例代码
2013/10/21 Javascript
深入理解Javascript动态方法调用与参数修改的问题
2013/12/10 Javascript
jquery实现瀑布流效果分享
2014/03/26 Javascript
模拟一个类似百度google的模糊搜索下拉列表
2014/04/15 Javascript
禁用Enter键表单自动提交实现代码
2014/05/22 Javascript
基于JavaScript创建动态Dom
2015/12/08 Javascript
js替换字符串中所有指定的字符(实现代码)
2016/08/17 Javascript
JS 判断某变量是否为某数组中的一个值的3种方法(总结)
2017/07/10 Javascript
详解微信小程序Page中data数据操作和函数调用
2017/09/27 Javascript
Node实战之不同环境下配置文件使用教程
2018/01/02 Javascript
JS实现从对象获取对象中单个键值的方法示例
2019/06/05 Javascript
微信自定义分享链接信息(标题,图片和内容)实现过程详解
2019/09/04 Javascript
微信小程序 wxParse插件显示视频问题
2019/09/27 Javascript
vue-cli 为项目设置别名的方法
2019/10/15 Javascript
Element Dialog对话框的使用示例
2020/07/26 Javascript
element-ui和vue表单(对话框)验证提示语(残留)清除操作
2020/09/11 Javascript
详解Python中break语句的用法
2015/05/14 Python
python互斥锁、加锁、同步机制、异步通信知识总结
2018/02/11 Python
一百多行python代码实现抢票助手
2018/09/25 Python
Opencv-Python图像透视变换cv2.warpPerspective的示例
2019/04/11 Python
Python 3.8正式发布重要新功能一览
2019/10/17 Python
Kangol帽子官网:坎戈尔袋鼠
2018/09/26 全球购物
以设计师精品品质提供快速时尚:Mostata
2019/05/10 全球购物
安德玛菲律宾官网:Under Armour菲律宾
2020/07/28 全球购物
伦敦鲜花递送:Flower Station
2021/02/03 全球购物
英国发展最快的在线超市之一:Click Marketplace
2021/02/15 全球购物
高中毕业自我鉴定
2013/12/16 职场文书
学校交通安全责任书
2014/08/25 职场文书
2015年公务员个人工作总结
2015/04/24 职场文书
最感人的道歉情书
2015/05/12 职场文书
无罪辩护词范文
2015/05/21 职场文书