如果您对 webpack 有一定的了解,那么您可能并不是本片文章的目标人群。这篇文章是本人学习 webpack 的一个小结,主要是面向 webpack 萌新的。不过也非常欢迎 老司机 提出不同的观点,指出文章的错误 。
由于本篇文章主要是面向初学者,为了便于理解和学习,本篇文章并不会涉及到与性能优化相关的内容(如tree-shaking
、split-chunk
等)。
推荐使用 yarn
代替 npm
作为包管理工具。yarn
与 npm
的比较可以参考 《npm和yarn的区别,我们该如何选择?》 这篇文章。
- 新建工作目录,文件夹名称随意。
- 进入工作目录,执行
yarn init -y
生成package.json
文件。
参考代码
mkdir webpack-test & cd webpack-test/
yarn init -y
本质上,webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。从 webpack v4.0.0 开始,可以不用引入一个配置文件。然而,webpack 仍然还是高度可配置的。在开始前你需要先理解四个核心概念:
入口起点(entry point)指示 webpack 应该使用哪个模块,来作为构建其内部依赖图的开始。进入入口起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的。
在 webpack 配置中有多种方式定义 entry 属性:
entry: string|Array
entry: './src/index.js' // string
etnry: ['./src/foo.js', './src/bar.js'] // Array
entry
属性的单个入口语法 (entry: './src/index.js
),是下面的简写:entry: {
main: './src/index.js'
}
entry: {[entryChunkName: string]: string|Array}
entry: {
app: './src/app.js',
vendors: './src/vendors.js'
}
配置 output 选项可以控制 webpack 如何向硬盘写入编译文件。
在 webpack 中配置 output 属性的最低要求是,将它的值设置为一个对象,包括以下两点:
filename
用于输出文件的文件名。output: {
filename: 'bundle.js',
path: '/home/proj/public/assets'
}
如果 entry
配置了多个 chunk
,则应该使用占位符(substitutions)来确保每个文件具有唯一的名称。
example:
{
entry: {
app: './src/app.js',
search: './src/search.js'
},
output: {
filename: '[name].js',
path: __dirname + '/dist'
}
}
// 写入到硬盘:./dist/app.js, ./dist/search.js
默认情况下,webpack 只能处理 JavaScript 和 json 文件,loader 让 webpack 能够去处理那些非 JavaScript 文件。可以将 loader 看作是模块转换器,loader 可以将相应的文件处理成 webpack 能够处理的有效模块。
在 webpack 中配置 loader 需要指定两个属性:
1. test
属性,用于标识出应该被对应的 loader 进行转换的某个或某些文件。
2. use
属性,表示进行转换时,应该使用哪个 loader。
example:
module: {
rules: [
{ test: /\.txt$/, use: 'raw-loader' }
]
}
上面的代码,等于是告诉 webpack:
“嘿,webpack 编译器,当你碰到「在 require()/import 语句中被解析为 ‘.txt’ 的路径」时,在你对它打包之前,先使用 raw-loader 转换一下。”
在完成基本配置之后,需要支持以下几个功能:
sass
文件,并自动为部分 css 代码添加浏览器厂商前缀。.vue
文件中编写 Vue
代码。Babel
转换 js 代码,使非现代浏览器(IE
)能支持一些 js 高级特性和语法。
yarn add webpack webpack-cli --dev
在与 package.json
文件同级目录下,新建 webpack.config.js
文件和 src/index.js
文件。在 webpack.config.js
中编写 webpack 相关配置代码:
// webpack.config.js
const path = require('path')
module.exports = {
entry: {
app: './src/index.js', // 指定 app 这个 chunk 的入口文件
},
output: {
/**
* [name] 是占位符,与对应的 chunk 名称相同 (app)
* [hash:8] 表示8位随机 hash 字符串
*/
filename: '[name].[hash:8].js',
/**
* webpack 会在 `webpack.config.js` 同级目录写入 `dist` 文件夹,
* dist 文件夹内包含了 webpack 打包后的文件
*/
path: path.resolve(__dirname, 'dist'),
}
}
为了能方便运行打包后的 js 文件,我们需要安装 html-webpack-plugin
和 webpack-dev-server
。
html-webpack-plugin
能生成 html 文件,生成的 html 文件会自动加载 webpack 打包出的 css
、js
等文件。webpack-dev-server
能够快速生成一个简易服务器,方便我们本地开发。
yarn add html-webpack-plugin webpack-dev-server --dev
安装完成之后,为 webpack.config.js
和 package.json
追加代码:
// webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: {
app: './src/index.js',
},
output: {
filename: '[name].[hash:8].js',
path: path.resolve(__dirname, 'dist'),
},
plugins: [
new HtmlWebpackPlugin(),
],
devServer: {
port: 3000, // 指定打开的页面端口
open: true, // 打包结束之后,自动打开页面
},
}
// package.json
{
"name": "webpack-demo",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"start": "webpack-dev-server"
},
"license": "MIT",
"devDependencies": {
"html-webpack-plugin": "^4.3.0",
"webpack": "^4.43.0",
"webpack-cli": "^3.3.11",
"webpack-dev-server": "^3.11.0"
}
}
执行 yarn start
,webpack 会打开默认浏览器窗口,此时就能看到 src/index.js
中的代码已经生效。
安装 style-loader
、css-loader
、sass-loader
和 sass
yarn add style-loader css-loader sass-loader sass --dev
追加相应配置:
module.exports = {
...
module: {
rules: [
{
test: /\.(c|sa|sc)ss$/,
use: ['style-loader', 'css-loader', 'sass-loader'],
},
],
},
...
}
当 import 了一个 css
、sass
或 scss
文件之后,这个文件会先被 sass-loader
处理,sass-loader
会将文件中的 sass
语法(比如嵌套语法、mixin
等)解析成普通 css
代码字符串,再将解析后的字符串交给 css-loader
处理,最后交给 style-loader
处理,style-loader
会将处理后的样式代码,作为内联样式插入到 head
标签中。
由于某些浏览器的存在,在编写 css3 的代码时,需要为部分 css 代码增加浏览器厂商前缀。手动添加实在是太麻烦了,所幸 autoprefixer
能帮开发者做这件事。
在 webpack 中使用 autoprefixer
还需要安装 postcss-loader
:
yarn add postcss-loader autoprefixer --dev
安装完成之后,需要修改 webpack 中与 css 相关的配置:
// webpack.config.js
module: {
rules: [
{
test: /\.(c|sa|sc)ss$/,
use: [
'style-loader',
'css-loader',
{
loader: 'postcss-loader',
options: {
plugins: [
require('autoprefixer'),
]
},
},
'sass-loader'
],
}
],
},
并在 package.json
中增加 browserslist
字段:
// package.json
{
...
"browserslist": ["last 2 versions", "not ie <= 11"],
...
}
这段配置的意思是:打包后的 css 和 js 需要支持的每个浏览器最新的两个版本,并且需要支持 IE11+。broswerslist 能约束 autoprefixer
和 babel
编译后的代码 尽可能 支持哪些浏览器。
试着写一段 flex
代码,autoprefixer
为 display: flex
这段代码添加了浏览器厂商前缀 -webkit-
和 -ms-
:
由于 webpack 默认只能处理 js 和 json 文件,在遇到图片、字体等静态文件时,可以使用 file-loader
正确处理静态文件的路径。
安装 file-loader
,编写 webpack 配置:
yarn add file-loader --dev
// webpack.config.js
module: {
rules: [
...
{
test: /\.(jpg|jpeg|png|gif|svg)$/,
use: 'file-loader',
},
],
}
.vue
文件为了使 webpack 能处理 .vue
文件,需要安装 vue-loader
和 vue-template-compiler
:
yarn add vue-loader vue-template-compiler --dev
修改 webpack 配置:
// webpack.config.js
const VueLoaderPlugin = require('vue-loader/lib/plugin')
module.exports = {
...
module: {
rules: [
{
test: /\.vue$/,
use: 'vue-loader',
},
],
},
...
plugins: [
...
new VueLoaderPlugin()
]
由于 new Vue 时需要指定 el
,所以我们需要使用自己的 html
模版文件。
新建 src/index.html
,在 body
中写入 :
修改 webpack 配置,指定生成的 html
文件的模版:
现在让我们来试试是否可以使用 .vue
单文件编写 Vue
的代码了:
新建 src/App.vue
文件:
Hello World
在 src/index.js
中引入 src/App.vue
,并使用:
import Vue from 'vue'
import App from './App.vue'
new Vue({
el: '#app',
render: h => h(App)
})
执行 yarn start
,不出意外的话,此时页面已经成功渲染 src/App.vue
组件了。
babel
是一个 JavaScript 编译器,能将 ES6+
的代码编译成浏览器能认识的ES3
、ES5
代码,没有 babel
很多前端开发者可能都没有办法好好写代码了。
为了使 webpack 能和 babel
协作,需要安装 @babel/core
、@babel/preset-env
、babel-loader
和 core-js
:
yarn add @babel/core @babel/preset-env babel-loader core-js --dev
安装完成之后,增加相关配置:
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
modules: false, // 不转换成 commonjs 模块
useBuiltIns: 'usage', // 按需 polyfill
corejs: 3,
}
]
]
}
}
}
现在让我们试试 babel
按需 polyfill 是否生效了。在入口文件 src/index.js
添加: console.log([1, 2, 3].includes(1))
,执行 yarn start
,由于 broswerslist
指定了需要兼容 IE11+ 的浏览器,发现 core-js/modules/es.array.includes.js
这个模块也被 bundle 进了 app
这个 chunk。由此可以推断,babel 相关的配置已经生效了。
babel
还可以添加一些功能强大的插件,以下是添加 @babel/plugin-proposal-optional-chaining
插件的例子:
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
modules: false, // 不转换成 commonjs 模块
useBuiltIns: 'usage', // 按需 polyfill
corejs: 3,
},
],
],
plugins: [
// https://babeljs.io/docs/en/babel-plugin-proposal-optional-chaining
// 支持这样语法:const obj = {}; const result = obj?.a?.b?.c?.d;
'@babel/plugin-proposal-optional-chaining',
]
}
}
}
一般情况下,我们希望生产环境和开发环境能有一些区别的。比如生产环境需要代码压缩,开发环境为了便于代码调试,需要开启 source-map
。以及如何做 split-chunk
、tree-shaking
、HotModuleReplacement
等,网上相关的文章很多,本篇文章就不涉及这部分内容了。
感谢你的阅读,如果你发现文章有错误,欢迎在评论区指出。
参考链接
- webpack官网