Webpack 食用手册
还是老老实实写一篇中文的使用手册吧
本篇不讨论原理、模块化之类的,只记录如何使用
- Webpack 食用手册
- 安装
- 创建配置
- 入口 Entry
- 输出 Output
- publicPath
- chunkFilename
- 处理器 Loader
- 三种方式使用 Loaders
- 特性
- 常用 loaders
- css
- JavaScript
- 图片 & 字体
- 插件 Plugin
- 使用
- 常用插件:
- html-webpack-plugin
- uglifyjs-webpack-plugin
- mini-css-extract-plugin
- optimize-css-assets-webpack-plugin
- 模式 Mode
- 常用命令
- devtool属性
- devServer
- resolve
- 一份配置
- 再来一份
安装
npm init
都安装在本地开发环境 --save-dev
npm install -D webpack webpack-cli
npm install -D webpack-dev-server
安装 webpack 服务器
创建配置
项目根目录下创建webpack.config.js
文件
基本大致内容如下
modules.export={
entry:{
// 入口文件
},
output:{
// 出口文件
},
module:{
// loaders
rules:[{},{},{}]
},
plugins:[
// 插件
],
devtool: ...
devServer: {...}
resolve:{...}
}
入口 Entry
webpack 创建依赖图的入口文件,递归遍历这些文件,然后用 Loaders 去进行对应处理
官网给了三种方式:
-
字符串
entry: './src/index.js'
-
对象(多个入口作为属性,都会打包,互不依赖)
entry: { main: './src/app.js', vendor: './src/vendor.js' }
这里的属性值对应着入口文件的
name
-
数组
entry: ["app.js","main.js"];
使用场景:
- 分离 App 和 Vendor 的入口( App 就是应用主程序, Vendor 可以认为是通用的库,分发给 App 中不同模块使用)
- 多页应用,每个页面依赖的模块都不同
输出 Output
打包后输出的文件
output: {
path: path.resolve(__dirname, '/dist'), // 打包后文件所在的路径
filename: '[name].[hash].bundle.js', // 文件名
publicPath: '/assets/', // 上线时的路径,用于线上
chunkFilename: 'js/[name].bundle.js', // 按需加载模块输出的文件名
}
主要来看看下面两个属性
publicPath
这个路径配置之后,例如/assets/
,会访问www.xxx.xxx/assets/yyy.js
来获取输出的文件
这个其实在之前部署项目的时候遇到过,当初需要将项目部署在主域名的一个子路径下,可以通过 www.coyoo.xyz/wisdom 来访问,在 nginx
中将打包后的文件放在对应wisdom
目录即可,然后发现所有获取的静态资源都挂了,因为在index.html
中访问的都是没有子路径的资源。
由于使用的是quasar
ui框架,之后查阅了资料发现需要在配置文件中的build
中配置publicPath: '/wisdom'
,打包也是基于webpack
的,所以现在就清楚了。。
chunkFilename
在动态加载(按需加载)的时候,用注释import(/* webpackChunkName: 'chunk'*/chunkModule)
来给输出对象指定名字
处理器 Loader
浏览器只认识那三剑客,所以 webpack 可以通过 loaders 将其他语言处理成浏览器认识的语言。能够分析不同类型的文件,反正都认为是模块嘛,甚至可以在 js 模块中import
css 和 图片!
基础结构:
module:{
rules:[
{
test:/\.xxx$/,//以 .xxx 结尾的文件
use: [{loader: 'xxx-loader'}] // 从最后一个往前的顺序执行
loader: "xxx-loader",
exclude: {排除的路径},
include: {包含的路径},
options: {Loader 配置}
}
]
}
三种方式使用 Loaders
不需要引入,指明名字,配置一下就行
-
module.rules
中配置module.exports = { module: { rules: [ { test: /\.css$/, use: [ // style-loader { loader: 'style-loader' }, // css-loader { loader: 'css-loader', options: { modules: true } }, // sass-loader { loader: 'sass-loader' } ] // 这个顺序是从下至上的 } ] } };
-
在 import 的时候用 inline 方式:
import Styles from 'style-loader!css-loader?modules!./styles.css';
// 配置中 {loader: "style-loader!css-loader?importLoaders=1!less-loader"}
用
!
分割 loaders -
CLI 的方式
webpack --module-bind pug-loader --module-bind 'css=style-loader!css-loader
特性
- 可以将加载起链式串起,顺序都是从右往左(数组, inline ),为感觉这个原因可以解释为:正序读取 loaders ,然后处理成
loaderA(loaderB(loaderC(loaderD( source ))))
- 异步/同步执行
- 插件给 loader 赋能
- 可以输出任何文件,在 node 环境中可以干很多事情
常用 loaders
css
-
style-loader
通过注入
style
标签将 CSS 添加到 DOMnpm install style-loader --save-dev
-
css-loader
css-loader 像 import / require()一样解释@import 和 url()并解析它们。
npm install css-loader --save-dev
-
postcss-loader
补充不兼容的 css 属性 的浏览器前缀
npm install postcss-loader postcss-preset-env cssnano --save-dev # 后者会在 package.json 找 browserslist
package.json: 默认找生产环境(可以设置 process.env.NODE_ENV = 'development')
"browserslist": { "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version", ], "production": [ ">0.01%", // 99.99% 的兼容 "not dead", // 活着的 "not op_mini all" ] }
webpack.config.js
{ loader: 'postcss-loader', options: { ident: 'postcss', plugins: () => [ require('postcss-preset-env')(), require('cssnano')() // 可以压缩 ] } }
可以参考官方https://github.com/postcss/postcss-loader
-
less/sass-loader
npm install less --save-dev npm install less-loader --save-dev
JavaScript
babel
npm install -D babel-core babel-loader babel-preset-es2015
图片 & 字体
-
file-loader 用于压缩文件
// module.rules: { test: /\.(jpg|svg|png|gif|jpeg)$/, use: ['file-loader'], }, // ...
字体:
{ test: /\.(woff|woff2|eot|ttf|otf)$/, use: [ 'file-loader', ], }
-
url-loader 将会 url 转换成二进制编码(这个包依赖于 file-loader )
npm install --save-dev url-loader
modules: { rules: [ { test: /\.(jpe?g|png|gif)$/, use: [ { loader: 'url-loader', options: { esModule: false, // 不加的话会有这种情况 img属性src="[object Module]" // 图片大小小于10kb的都会转为 base64 编码 来减少http请求 limit: 1024 * 10, // 当大于100kb时候,将文件打包到publicPath中 outputPath: 'images', // 将文件打包到哪里 publicPath: '/images/', name: '[name].[hash:8].[ext]' // 取前 8 位 hash } } ] } ] }
base64 是啥:一种编码数据的方式,直接将图片二进制内容编码到 URL 中,浏览器直接可以从中读出图片,不需要发送请求了。注意,大的图片还是发请求吧, URL 太长了。转换成字符串之后大小还可能会更大。 8 - 12 kb 比较适合。
最终打包成:
data:image/png;base64,............
的数据data:
;base64, -
html-loader
专门负责引入 html 中的 img 标签,从而能被 url-loader 引入
{
test: /\.html$/,
use: 'html-loader',
}
会有小问题, url-loader 默认是 es module 解析的, html-loader 是 commonJS 解析的,关闭URL的 es6 模块化
上面的例子已经关闭了esModule: false
。但是好像开了也没问题。。。
如果使用框架啥的,比如 vue ,直接 import 进来放到 data 里面作为字符串,也是会被 url-loader 处理的
插件 Plugin
Plugins are the backbone of webpack.
——来自官网
一个插件是一个 js 对象,必须有 apply
方法
使用
需要引入模块,生成一个对象
plugins: [
new webpack.ProgressPlugin(),
new HtmlWebpackPlugin({template: './src/index.html'})
]
plugins: [
// for customize the option, you need to create an instance
new HtmlWebpackPlugin({template: './src/index.html'})
]
常用插件:
html-webpack-plugin
主要用于生成 HTML,可以规定 模板 HTML,也可以为 模板传入参数,压缩文件等
npm install --save-dev html-webpack-plugin
配置很多
new htmlWebpackPlugin({
// 打包后的文件名
filename: "index.html",
// 模板 复制这个文件 自动引入所有的打包后资源 输出到目标
template: "index.html",
// 为 true 自动生成 script 标签添加到 html 中
// 或者写 body/head 标签名
inject: false,//js 的注入标签
// 通过<%= htmlWebpackPlugin.options.title %>引用
// 自动会修改到模版
title: "参数 title",
//通过<%= htmlWebpackPlugin.options.date %> 引用
date: new Date()
//网站的图标
favicon: 'path/to/yourfile.ico'
//生成此次打包的 hash
//如果文件名中有哈希,便代表有 合理的缓冲
hash: true,
//排除的块
excludeChunks: [''],
//选中的块 与入口文件相关
chunks: ['app','people'],
//压缩
minify:{
removeComments: true, // 移除注释
collapseWhitespace: true, // 折叠空格
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true,
}
}),
用 ejs 的语法使用
<%= htmlWebpackPlugin.options.date %>
uglifyjs-webpack-plugin
mini-css-extract-plugin
提取 js 中的 css 样式到文件,在 html 中用 link 引入
npm install -D mini-css-extract-plugin
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
// 在loader 也要改
{
test: /\.css$/,
use: [
// 'style-loader', // 不需要创建标签了
MiniCssExtractPlugin.loader,
'css-loader'
],
},
// plugins 中
plugins: [
new MiniCssExtractPlugin(),
]
optimize-css-assets-webpack-plugin
压缩 css 成一行,用 cssnano 在 postcss 中也可以的
// plugins:
new OptimizeCssAssetsWebpackPlugin()
模式 Mode
mode: 'development' // 'production'
选项 | 描述 | 特点 |
---|---|---|
development | 会设置 process.env.NODE_ENV 为 development ,启用 NamedChunksPlugin 和 NamedModuluesPlugin | 让代码能本地调试 |
production | 会设置 process.env.NODE_ENV 为 production ,启用各种优化插件 | 优化线上环境的代码 |
生产模式下:
- css 需要在 js 中分离出来(之前是导入到 js 文件然后生成
标签,会导致闪屏)
- 代码压缩
- 兼容性
这些操作放在生产环境下做,耗时比较多,不适合在开发模式。
常用命令
webpack
: 启动后寻找webpack.config.js
进行打包webpack -w
: 监控代码变化webpack -p
: 对打包后的文件进行压缩,利于线上发布webpack -d
: 提供 source map ,方便调试webpack -colors
: 输出带颜色,好看webpack -profile
: 输出性能数据
devtool属性
devServer
前面下载过npm install -D webpack-dev-server
devServer: {
contentBase: path.join(__dirname, 'dist'),
port: 8088,
hot: true, // 热更新
inline: true, // reload when file changes
historyApiFallback: true, // fallback to index.html when missing other files
open: true, // 自动打开浏览器
compress: true, // 启用 gzip 压缩
},
用 npx 启动npx webpack-dev-server
resolve
自动扩展文件名,可以在导入的时候不写后缀名
resolve: {
extensions: ['.less', '.mjs', '.js', '.json']
}
将路径取别名,方便引入
resolve: {
alias: {
Utilities: path.resolve(__dirname, 'src/utilities/'),
Templates: path.resolve(__dirname, 'src/templates/')
}
}
一份配置
const path = require('path'),
webpack = require('webpack'),
htmlWebpackPlugin = require('html-webpack-plugin'),
ExtractTextPlugin = require('extract-text-webpack-plugin'),
marked = require('marked'),
renderer = new marked.Renderer(),
CleanWebpackPlugin = require('clean-webpack-plugin'),
OpenBrowserPlugin = require('open-browser-webpack-plugin');
const MyConfig = {
entry: {
app: './src/js/app.js',
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'js/[name].js',
// 上线时的公共路径
// publicPath: "http://cdn.com/",
// 按需加载模块时输出的文件名称
// chunkFilename: 'js/[name].js'
},
/* 生成调试用的 source-map */
devtool: 'eval-source-map',
// [inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map
// 开发环境用 eval-source-map / eval-cheap-module-source-map
// 生产环境 source-map
devServer: {
contentBase: './dist', //本地服务器所加载 的页面所在的目录
historyApiFallback: true, //再找不到文件 的时候默认指向 index.html,
inline: true, //当源文件改变时会自动刷新页面
hot: true, //热加载开启
port: 8080, // 设置默认监听端口
},
resolve: {
//自动扩展文件后缀名,意味着我们 require 模块可 以省略不写后缀名
extensions: ['.js', '.html', '.css', '.txt', 'less', 'ejs', 'json'],
//模块别名定义,直接 require('AppStore') 即可,方便后续直接引用别名
alias: { Temp: path.resolve(__dirname, 'src/templates/') },
},
module: {
rules: [
{
test: /\.(less|css)?$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: [
{ loader: 'css-loader? modules', options: { importLoaders: 1 } },
{
loader: 'postcss-loa der',
options: { plugins: (loader) => [require('autoprefixer')()] },
},
{ loader: 'less-loader' },
],
}),
exclude: path.resolve(__dirname, './node_modules'),
},
{
test: /\.js$/,
loader: 'babel-loader',
exclude: path.resolve(__dirname, './node_modules'),
include: path.resolve(__dirname, './src'),
options: { presets: ['latest'] },
},
{
test: /\.html$/,
loader: 'html-loader',
include: path.resolve(__dirname, './src/layer'),
exclude: path.resolve(__dirname, './node_modules'),
},
{
test: /\.ejs$/,
loader: 'ejs-loader',
include: path.resolve(__dirname, './src/layer'),
exclude: path.resolve(__dirname, './node_modules'),
},
{
test: /\.(png|jpe?g|gif|svg| woff|woff2|ttf|eot|otf)$/i,
loaders: [
'file-loader',
'url-loader?limit=8192',
{
loader: 'image-webpack-loader',
options: {
gifsicle: { interlaced: false },
optipng: { optimizationLevel: 7 },
pngquant: { quality: '65-90', speed: 4 },
mozjpeg: { progressive: true, quality: 65 },
webp: { quality: 75 },
},
},
],
exclude: path.resolve(__dirname, './node_modules'),
},
],
},
plugins: [
//打包前 先删除 dist 目录下的文件
new CleanWebpackPlugin(['dist'], {
root: __dirname, //指定插件根目录 位置
verbose: true, //开启在控制台输出 信息
dry: false, //启用删除文件
}),
//生成 html
new htmlWebpackPlugin({
filename: 'index.html', //文件名
template: 'index.html', //模板
inject: false, //js 的注入标签
//这个配置项为 true 表示自动把打包出来的文 件通过自动生成 script 标签添加到 html 中
title: '参数 title', //通过<%= htmlWebpackPlugin.options.title %> 引用
date: new Date(), //通过<%= htmlWebpackPlugin.options.date %>引 用
//favicon: 'path/to/yourfile.ico'
// excludeChunks: [''],//排除的块
// chunks: ['app','people']//选中的 块
/*
minify:{ //压缩
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true,
}
*/
}),
//防止 CSS 文件混乱,单独生成一个 css 文件
new ExtractTextPlugin('./css/[name] .min.css'),
//在每个生成的 chunk 顶部添加 banner
new webpack.BannerPlugin('Anthor:Simon'), //添加一个显示版权声明的插 件
new webpack.optimize.UglifyJsPlugin({
compress: {
//额外的压缩选项
warnings: false,
},
// mangle: { 排除不想要压缩的对象名称
// except: ['$super', '$', 'exports', 'require', 'module', '_']
// },
}), //压缩 js
//定义全局变量
new webpack.DefinePlugin({
__DEV__: JSON.stringify(JSON.parse(process.env.DEBUG || 'false')),
}),
//使用 ProvidePlugin 加载的模块在使用时将不再 需要 import 和 require 进行引入
//全局自动加载模块
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
}),
//打开服务器后 会自动打开浏览器
new OpenBrowserPlugin({ url: 'http://localhost:8080' }),
//排序输出,为组件分配 ID
new webpack.optimize.OccurrenceOrderPlugin(),
// 启用 HMR 热加载插件
new webpack.HotModuleReplacementPlugin(),
// 打印日志信息时 webpack 默认使用模块的数 字 ID 指代模块,不便于 debug,
// 这个插件可以将其替换为模块的真实路径
new webpack.NamedModulesPlugin(),
/*提取 Chunks 中的公共内容
new webpack.optimize.CommonsChunkPlugin ({
name: ["vendor", "manifest"], // vendor libs + extracted manifest
minChunks: Infinity,
}),*/
/*拷贝资源插件 适用于线上场景
new CopyWebpackPlugin([{
from: __dirname + '/src/public'
}]),*/
],
};
module.exports = MyConfig;
再来一份
/*
* @Author: CoyoteWaltz
* @Date: 2020-06-27 16:13:35
* @LastEditTime: 2020-06-27 16:58:33
* @LastEditors: CoyoteWaltz
* @Description: 生产环境的一份配置
*/
const { resolve } = require('../copy.config');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const htmlWebpackPlugin = require('html-webpack-plugin');
// css js html
// process.env.NODE_ENV = 'development';
// 抽离代码
const commonCssLoader = [
MiniCssExtractPlugin.loader,
'css-loader',
{
// 兼容性处理 记得在package.json 中定义 browserslist
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: () => [require('postcss-preset-env')(), require('cssnano')()],
},
},
];
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/bundle.js', // js目录下的 bundle.js
path: resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /\.css$/,
use: [...commonCssLoader],
},
{
test: /\.less$/,
use: [
...commonCssLoader,
'less-loader', // 逆序执行 loaders 的
],
},
// 多个匹配同一个文件的 loader 的执行顺序 先执行 eslint 再 babel
{
// 下载 eslint-config-airbnb-base eslint eslint-plugin-import
// js eslint 用 airbnb 的规则 package.json 中 eslintConfig
test: /\.js$/,
exclude: path.resolve(__dirname, './node_modules'),
loader: 'eslint-loader',
// 优先执行 如果有多个呢 用 use
enforce: 'pre',
options: {
fix: true, // 自动修复错误
},
},
{
// js eslint 用 airbnb 的规则 package.json 中 eslintConfig
test: /\.js$/,
exclude: path.resolve(__dirname, './node_modules'),
loader: 'babel-loader',
include: path.resolve(__dirname, './src'),
options: { presets: ['latest'] },
// options: {
// presets: [
// [
// '@babel/preset-env', // 基本语法转换 但是 promise 这种高级的就不行
// // 按需进行兼容性的映入 core-js
// {
// useBuiltIns: 'usage', // 按需加载
// codejs: { version: 3 }, // 指定第三个版本
// target: {
// chrome: '60', // 指定兼容性到某个版本
// firefox: '50',
// ie: '9',
// safari: '10',
// edge: '17',
// },
// },
// ],
// ],
// },
},
{
test: /\.(jpe?g|svg|png|gif|woff|woff2|ttf|eot|otf)$/,
use: [
{
loader: 'url-loader',
options: {
esModule: false, // 不加的话会有这种情况 img属性src="[object Module]"
// 图片大小小于8kb的都会转为 base64 编码 来减少http请求
limit: 1024 * 8, // 当大于100kb时候,将文件打包到publicPath中
outputPath: 'images', // 将文件打包到哪里
// publicPath: '/images/',
name: '[name].[hash:10].[ext]',
},
},
{
loader: 'image-webpack-loader',
options: {
gifsicle: { interlaced: false },
optipng: { optimizationLevel: 7 },
pngquant: { quality: '65-90', speed: 4 },
mozjpeg: { progressive: true, quality: 65 },
webp: { quality: 75 },
},
},
],
exclude: path.resolve(__dirname, './node_modules'),
},
{
test: /\.html$/,
use: 'html-loader',
},
{
exclude: /\.(js|css|less|html|jpe?g|gif|png)$/,
use: 'file-loader',
options: {
outputPath: 'media',
},
},
],
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/bundle.css',
}),
new htmlWebpackPlugin({
title: 'output',
template: './src/index.html',
minify: {
//压缩
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true,
},
}),
],
mode: 'production',
};