引言
最近在学习webpack,发现好多知识点,之前一点都没有接触过,如babel、core-js、browserslist等等。以前习惯了使用cli构建项目,很多东西不用考虑,拿来就用,这样的码农是不会有能力提升的,必须了解更多的知识点,才能成为一位出色的前端工程师。
我大致梳理了一下我的学习历程,将它简单的归类,以解决问题的路径娓娓道来。当然我的学习也不算完整,还在继续努力,也请各位大佬多多指导。
学习webpack首先必须拥有的基础是:
1.了解node.js是什么;
2.了解npm包管理器是什么以及package.json的常用属性的意义;
在此基础上,我们来一步步的学习webpack,以及如何使用webpack进行打包。
webpack 简介
webapck是一款前端资源打包工具,其最核心的功能是解决模块之间的依赖问题。听起来是不是很耳熟?对,没错,如果你学习过AMD规范、CommonJS规范,你会发现webpack是在这些规范的基础上发展出来的开源工具,解决前端开发人员模块化到工程化的问题。(PS:没有了解过AMD、CommonJS的小伙伴建议去简单了解一下,因为这是js模块化的基础,了解该基础才能理解前端模块化,而进一步理解前端工程化,才能明白webpack到底在干什么。)
这里简单说一下js模块化。很多小伙伴从切图仔进阶到前后端分离的前端工程模式时,都有些犯怵,一下子不理解前端工程到底是个什么概念,其实前端工程化说白了,就是在开发中大型web应用时,页面的交互非常频繁,很多计算、数据处理、业务代码都放在客户端(浏览器)进行处理了。那这么多的代码,怎么进行管理和维护呢,难道还是按照不同的页面标签的先后顺序么?就不能一个html,只引用一个
,其他的都在这个
里面去解决么?这时,就发展出了js模块化,也就出现了AMD、CommonJS等这些规范(Node.js就是根据CommonJS规范处理模块的,新版的jQuery是根据UMD规范来进行编写的)。有了这些规范,我们就可以很从容的管理js与js之间的依赖关系了,而webpack正是将多个模块打包合并成一个js(或多个)的工具,html里面也可以只引入一个
标签。
webpack不仅可以打包js,还可以将前端各种各样的资源整合起来进行打包,如:css、image、video等等...并且在打包的过程中对资源进行处理,如:代码压缩、代码合并等等...并利用各式各样的loader(预处理器)、plugins(插件),让你的代码自动完成ES6 => ES5、SCSS => css转化等功能,。总的来说,webpack现在已然是一款前端构建工具,可以构建你的前端开发环境,并在配置好各项设置后,可以集中精力聚焦于开发业务,其他的事就交给webpack帮你来处理。
安装
在安装webpack之前,我们需要确保在本机上已经安装了Node.js。果没有安装,请去Node.js官网下载。(https://nodejs.org/)
我们先初始化我们的项目文件夹。
npm init -y
初始化完毕后,我们打开webpack的官网:https://webpack.js.org/,阅读英文有困难的小伙伴,可以选择右上方的语言切换按钮,选择中文阅览。切换好语言后,我们再次点击导航栏中的“文档”栏目,并点击二级导航的“指南”页面。这时,左侧的菜单中,就已经出现了webpack的基础指南。
首先我们选择菜单-安装
出于学习的目的,大家可以选择全局安装。而如果出于项目制作的考虑,以及可能会用到Git进行版本控制和分享,我推荐大家进行本地安装。使用本地安装,webpack会存于node_modules文件夹内与devDependencies属性内,更方便项目文件迁移以及协同开发等情况。
使用webpack需要安装3个包,分别是
1.webpack(核心包)
2.webpack-cli(脚手架)
3.webpack-dev-server(开发服务器)
webpack核心包,顾名思义,是基础,必须使用的包。
webpack-cli脚手架,是可以帮助我们在使用webpack的时候,减少一些需要手动配置的选项,更方便我们使用自定义配置的工具,从wepback v4.0开始必须安装
webpack-dev-server是我们在开发环境时,不可能每一次调试都重新构建一次。所以一个热重载的服务器就很有必要。
使用npm命令:
npm install --save-dev webpack webpack-cli webpack-dev-server
好的,安装成功。
配置文件
webpack 默认提供了一套基础配置,以供新手使用者快速上手使用。webpack 会假定项目的入口起点为 src/index,然后会在 dist/main.js 输出结果,并且在生产环境开启压缩和优化。
但是通常情况下,我们在制作项目时,还需要 webpack 提供给我们各种各样的复杂功能。而此时我们可以在项目根目录下创建一个 webpack.config.js 文件,webpack 会自动使用它。
const path = require('path');
module.exports = {
mode: "production", // "production" | "development" | "none" // Chosen mode tells webpack to use its built-in optimizations accordingly.
entry: "./app/entry", // string | object | array // 默认为 './src'
// 这里应用程序开始执行
// webpack 开始打包
output: {
// webpack 如何输出结果的相关选项
path: path.resolve(__dirname, "dist"), // string
// 所有输出文件的目标路径
// 必须是绝对路径(使用 Node.js 的 path 模块)
filename: "bundle.js", // string // 「入口分块(entry chunk)」的文件名模板
publicPath: "/assets/", // string // 输出解析文件的目录,url 相对于 HTML 页面
library: "MyLibrary", // string,
// 导出库(exported library)的名称
libraryTarget: "umd", // 通用模块定义 // 导出库(exported library)的类型
/* 高级输出配置(点击显示) */
},
module: {
// 关于模块配置
rules: [
// 模块规则(配置 loader、解析器等选项)
{
test: /\.jsx?$/,
include: [
path.resolve(__dirname, "app")
],
exclude: [
path.resolve(__dirname, "app/demo-files")
],
// 这里是匹配条件,每个选项都接收一个正则表达式或字符串
// test 和 include 具有相同的作用,都是必须匹配选项
// exclude 是必不匹配选项(优先于 test 和 include)
// 最佳实践:
// - 只在 test 和 文件名匹配 中使用正则表达式
// - 在 include 和 exclude 中使用绝对路径数组
// - 尽量避免 exclude,更倾向于使用 include
issuer: {
test,
include,
exclude
},
// issuer 条件(导入源)
enforce: "pre",
enforce: "post",
// 标识应用这些规则,即使规则覆盖(高级选项)
loader: "babel-loader",
// 应该应用的 loader,它相对上下文解析
// 为了更清晰,`-loader` 后缀在 webpack 2 中不再是可选的
// 查看 webpack 1 升级指南。
options: {
presets: ["es2015"]
},
// loader 的可选项
},
{
test: /\.html$/,
use: [
// 应用多个 loader 和选项
"htmllint-loader",
{
loader: "html-loader",
options: {
/* ... */
}
}
]
},
{
oneOf: [ /* rules */ ]
},
// 只使用这些嵌套规则之一
{
rules: [ /* rules */ ]
},
// 使用所有这些嵌套规则(合并可用条件)
{
resource: {
and: [ /* 条件 */ ]
}
},
// 仅当所有条件都匹配时才匹配
{
resource: {
or: [ /* 条件 */ ]
}
},
{
resource: [ /* 条件 */ ]
},
// 任意条件匹配时匹配(默认为数组)
{
resource: {
not: /* 条件 */
}
}
// 条件不匹配时匹配
],
/* 高级模块配置(点击展示) */
},
resolve: {
// 解析模块请求的选项
// (不适用于对 loader 解析)
modules: [
"node_modules",
path.resolve(__dirname, "app")
],
// 用于查找模块的目录
extensions: [".js", ".json", ".jsx", ".css"],
// 使用的扩展名
alias: {
// 模块别名列表
"module": "new-module",
// 起别名:"module" -> "new-module" 和 "module/path/file" -> "new-module/path/file"
"only-module$": "new-module",
// 起别名 "only-module" -> "new-module",但不匹配 "only-module/path/file" -> "new-module/path/file"
"module": path.resolve(__dirname, "app/third/module.js"),
// 起别名 "module" -> "./app/third/module.js" 和 "module/file" 会导致错误
// 模块别名相对于当前上下文导入
},
/* 可供选择的别名语法(点击展示) */
/* 高级解析选项(点击展示) */
},
performance: {
hints: "warning", // 枚举 maxAssetSize: 200000, // 整数类型(以字节为单位)
maxEntrypointSize: 400000, // 整数类型(以字节为单位)
assetFilter: function (assetFilename) {
// 提供资源文件名的断言函数
return assetFilename.endsWith('.css') || assetFilename.endsWith('.js');
}
},
devtool: "source-map", // enum // 通过在浏览器调试工具(browser devtools)中添加元信息(meta info)增强调试
// 牺牲了构建速度的 `source-map' 是最详细的。
context: __dirname, // string(绝对路径!)
// webpack 的主目录
// entry 和 module.rules.loader 选项
// 相对于此目录解析
target: "web", // 枚举 // bundle 应该运行的环境
// 更改 块加载行为(chunk loading behavior) 和 可用模块(available module)
externals: ["react", /^@angular\//], // 不要遵循/打包这些模块,而是在运行时从环境中请求他们
serve: { //object
port: 1337,
content: './dist',
// ...
},
// 为 webpack-serve 提供选项
stats: "errors-only", // 精确控制要显示的 bundle 信息
devServer: {
proxy: { // proxy URLs to backend development server
'/api': 'http://localhost:3000'
},
contentBase: path.join(__dirname, 'public'), // boolean | string | array, static file location
compress: true, // enable gzip compression
historyApiFallback: true, // true for index.html upon 404, object for multiple paths
hot: true, // hot module replacement. Depends on HotModuleReplacementPlugin
https: false, // true for self-signed, object for cert authority
noInfo: true, // only errors & warns on hot reload
// ...
},
plugins: [
// ...
],
// 附加插件列表
/* 高级配置(点击展示) */
}
以上文件是引用的 webpack 官网的“配置”一篇中的一个样例,通过上面的样例我们可以看出, webpack 的配置项真的是多如繁星,这也正是我们学习 webpack 的难点之一。我们会在后面的学习中,逐步弄明白每一个配置项具体有什么意义,能为我们干什么。
多配制文件
有时,我们会因为项目的不用需求,建立不同的配置文件,以供我们应对多种情况。这时,可以新建多个配置文件,如:build.conf.js、test.conf.js。并在package.json中进行这样的设置:
这样,我们就可以通过使用不同的npm命令,来使用不同的配置文件,进行相应打包操作了。
入口与输出
了解npm的小伙伴,肯定知道,package.json里有一个main属性,代表整个包的输出口,通过main指向的js文件,得到一个对象(或类或函数)。
我们通过CommonJS规范语法:
const XXX = require('XXX')'
来引入该模块,并使用它。
而webpack的入口与输出,与这个非常的类似。当我们使用webpack进行打包时,需要指定至少一个入口,并设置其对应的输出配置。比如:
const path = require('path')
module.exports = {
entry: "./src/main", // string | object | array // 默认为 './src'
// 这里应用程序开始执行
// webpack 开始打包
output: {
// webpack 如何输出结果的相关选项
path: path.resolve(__dirname, "dist/assets"), // string
// 所有输出文件的目标路径
// 必须是绝对路径(使用 Node.js 的 path 模块)
filename: "/[name].js", // string // 「入口分块(entry chunk)」的文件名模板
},
}
当我们配置好这些后,只需要命令webpack开始打包即可。
npx webpack
注意,此处因为我们并没有全局安装 webpack 和 webpack-cli ,所以我们无法直接使用 webpack 命令来进行打包,而使用了 npx 命令再转而使用 webpack 命令,此处只是为了向大家展示,是可以使用 webpack 命令直接进行打包的,但往往在实际项目开发的过程中,我们并不会这样使用。所以在后面的学习中,我们将只使用,package.json 中的 scripts 脚本命令,来进行打包的命令操作。而在 package.json 中的脚本命令,是不需要全局安装 webpack 的,在本地安装即可。也就是下面的配置:package.json
{
"scripts": {
"build":"webpack --config webpack.config.js"
}
}
然后使用npm run 脚本名称即可运行
npm run build