- 全局安装webpack 和 webpack-cli
npm install -g webpack webpack-cli
- 创建并进入项目文件
mkdir webpack-demo && cd $_
- 创建package.json
npm init
我这里 webpack
版本为 4.20.2
,webpack-cli
版本为 3.1.1
一、命令行打包
假设hello.js是我们需要打包的文件:
(1)不配置打包后文件的名称和路径,则默认在根目录下生成dist/main.js,
--mode
的可选值为development
和production
,默认值为development
。
webpack hello.js --mode=development
(2)配置打包后文件的名称和路径
webpack hello.js -o ./dist/hello.min.js --mode=production
(3)wepack支持AMD,CommonJS,ES6
以下为CommonJS模块示例:
根目录下 hello.js, 导出一个函数:
// hello.js:
module.exports=function () {
alert("hello")
}
根目录下 index.js, 使用require加载了hello.js,并执行了hello()函数
// index.js
var hello=require("./hello")
hello()
执行命令 webpack index.js --mode=development
, 打包结果如下:
(4)一些参数
--watch
: 当文件变动时,自动打包
--progress
: 看到打包的百分比进程
--colors
: 有颜色
--display-modules
: 列出引用的所有模块
--display-reasons
: 显示打包原因(哪里引用了被打包的文件)
(5)如何打包css文件
若直接在 index.js中 require ("./style.css")
,然后打包,会报错 “You may need an appropriate loader to handle this file type.”, 提示我们需要有合适的loader来处理这种类型的文件。
所以,我们安装两个loader ——css-loader
和 style-loader
:
// 安装css-loader 和 style-loader
npm install css-loader style-loader --save-dev
方式一:
在 index.js中 添加require("style-loader!css-loader!./style.css")
,意思是说,先通过css-loader处理style.css, 然后再将处理结果交给style-loader处理。css-loader 的作用是使得webpack可以处理css文件,而style-loader是将处理过后的css通过新建style标签的方式插入html中
// index.js
require("style-loader!css-loader!./style.css") // 注意两个loader的顺序
执行打包命令 webpack index.js --mode=development
即可成功打包css文件。这种方式的缺点是require 每个css文件都要加上loader, 不免麻烦。
方式二:
// index.js
require ("./style.css")
执行webpack index.js --mode=development --module-bind "css=style-loader!css-loader"
。
这种方式是通过--module-bind
指定打包css文件时需要的 loader,打包结果和方式一一样。
二、webpack 配置文件
(1)简单示例
一个最简单的webpack 配置:
// webpack.config.js
const path = require('path');
module.exports={
// mode:"development", // 模式,放在package.json中配置更好
entry: './index.js', // 打包入口文件
output:{
path:path.resolve(__dirname, 'dist'), // 打包输出路径
filename:"dist.min.js" // 打包输出文件名称
}
}
直接运行命令 webpack
即可打包, 这是因为 webpack默认会去查找名称为webpack.config.js的配置文件。若我们的配置文件不叫webpack.config.js,比如叫 webpack.config.dev.js, 则需通过--config
参数指定配置文件:
webpack --config webpack.dev.config.js
更简单的方式是在 package.json 中配置我们的打包命令:
// package.json
"scripts":{
"webpack-dev":"webpack --config webpack.config.js --mode=development --progress --colors --display-modules --display-reasons",
"webpack-prod":"webpack --config webpack.config.js --mode=production --progress --colors --display-modules --display-reasons"
}
然后运行npm run webpack-dev
或 npm run webpack-prod
即可完成对应配置的打包。
(2)entry 的 3 种配置方式
-
entry:"./index.js"
, 简单示例中的方式 -
entry:["./entry1.js","./entry2.js"]
, 适用于两个互不依赖的文件想打包到一起 - 多chunk方式(会打包生成多个文件)
以下为多chunk 配置示例:
// 文件目录
webpack-demo
|- index.js
|- webpack.config.js
|- package.json
|- js
|- a.js
|- b.js
|- c.js
//webpack.config.js
const path = require('path');
module.exports={
entry: {
index:"./index.js",
ab:["./js/a.js","./js/b.js"],
c:"./js/c.js",
},
output:{
path:path.resolve(__dirname, 'dist'),
filename:"[name]-[hash].js", // [name], [hash]为占位符, 还有[chunk-hash]
}
}
打包结果如下:
三、自动生成html页面(html-webpack-plugin插件的使用)
若使用 [hash] 或 [chunk-hash] 占位符的方式生成文件名称,每次生成的文件名称都是不一样的,那么在html中 如何通过外链的方式(<script src=" "></script>
)引入打包后的文件呢?或者我们有时候需要在不同的页面引入不同的 js文件,又该如何做呢?
我们可以使用 html-webpack-plugin 插件来做到这一切。
npm install html-webpack-plugin --save-dev
(1)按模板自动生成html页面
// webpack.config.js
const path = require('path');
var htmlWebpackPlugin=require("html-webpack-plugin")
module.exports={
entry: {
index:"./index.js",
ab:["./js/a.js","./js/b.js"],
c:"./js/c.js",
},
output:{
path:path.resolve(__dirname, 'dist'),
filename:"[name]-[hash].js",
},
plugins:[
new htmlWebpackPlugin({
filename:"index-[hash].html", // 生成的html文件名称
template:"index.html", // 以当前目录下的的 index.html 为模板来生成html
inject:"head", // script标签插入到 html中的位置,head/body/false, 默认为插入body, 若设为false,则生成的html中不会自动插入script标签
minify:{
removeComments:true, // 删除 html 中的注释
collapseWhitespace:true, // 删除 html 中空格
}
})
]
}
以上配置的打包结果是,以 index.html为模板生成一个新的 index-[hash].html,新的html 的 <header></header>
标签中以外链的方式引入了[index]-[hash].js, [ab]-[hash].js, [c]-[hash].js 三个打包后的js文件。
注:若 require("html-webpack-plugin")
时报错 Cannot find module 'webpack/lib/node/NodeTemplatePlugin,尝试本地安装webpack 或者 运行npm link webpack --save-dev
(该命令的作用是将一个任意位置的npm包链接到全局执行环境) 。
(2)生成多页面
可以在 plugins 中 new 若干个htmlWebpackPlugin实例以生成多页面,htmlWebpackPlugin还支持ejs语法自定义一些内容插入到html模板中。
webpack.config.js配置:
// webpack.config.js
const path = require('path');
var htmlWebpackPlugin=require("html-webpack-plugin")
module.exports={
entry: {
index:"./index.js",
ab:["./js/a.js","./js/b.js"],
c:"./js/c.js",
},
output:{
filename:"[name]-[hash].js",
path:path.resolve(__dirname, 'dist'),
publicPath:"http://cdn", // publicPath为项目中的所有资源指定一个基础路径
},
plugins:[
new htmlWebpackPlugin({
filename:"a.html",
template:"index.html" ,
title:"this is a.html", // title 将替换到模板中
chunks:["index","c"], // a.html 引入打包后的index.js 和 c.js
}) ,
new htmlWebpackPlugin({
filename:"b.html",
template:"index.html" ,
title:"this is b.html", // title 将替换到模板中
chunks:["ab"], // b.html 引入打包后的 ab.js
}),
new htmlWebpackPlugin({
filename:"c.html",
template:"index.html" ,
title:"this is c.html", // title 将替换到模板中
excludeChunks:["c"], // c.html 页面引入除c.js之外的其它js
})
]
}
模板index.html:
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!-- ejs 语法,渲染title -->
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
</body>
</html>
以上配置的打包结果是,生成了a.html 、 b.html 和 c.html 文件,a.html的<body></body>
标签中外链了index-[hash].js 和 c-[hash].js, 如下图所示:
(3)inline 的方式引入js
inline方式引入脚本可以减少http请求,以下演示如何实现部分脚本inline方式引入,部分脚本外链方式引入。
在“生成多页面”的例子上做修改,我们现在的目的是,让a.html, b.html,c.html 都以inline的方式引入index.js 脚本,其它脚本各页面以外链的方式按需引入。
webpack.config.js配置:
// webpack.config.js
const path = require('path');
var htmlWebpackPlugin=require("html-webpack-plugin")
module.exports={
...
plugins:[
new htmlWebpackPlugin({
filename:"a.html",
template:"index.html" ,
title:"this is a.html",
inject:false, // 设为 false,表示不自动插入脚本
chunks:["index","c"],
}) ,
new htmlWebpackPlugin({
filename:"b.html",
template:"index.html" ,
title:"this is b.html",
inject:false, // 设为 false
chunks:["index"], // 因为要演示inline方式引入index-[hash].js脚本,所以这里chunks中必须包含index
}),
new htmlWebpackPlugin({
filename:"c.html",
template:"index.html" ,
title:"this is c.html",
inject:false, // 设为 false
excludeChunks:["c"],
})
]
}
模板index.html:
// index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title><%= htmlWebpackPlugin.options.title %></title>
<script type="text/javascript">
<%= compilation.assets[htmlWebpackPlugin.files.chunks.index.entry.substr(htmlWebpackPlugin.files.publicPath.length)].source() %>
</script>
</head>
<body>
<% for (var k in htmlWebpackPlugin.files.chunks){%>
<% if(k !=="index") { %>
<script type="text/javascript" src="
<%= htmlWebpackPlugin.files.chunks[k].entry %>
"></script>
<% }%>
<% }%>
</body>
</html>
解释一下上图模板index.html的含义:
首先,compilation.assets
拿到的是所有的chunks,如下图所示:
其次,htmlWebpackPlugin.files
拿到的是对应页面的publicPath,chunks等信息,以下是a.html页面的htmlWebpackPlugin.files
数据,a.html 使用了index 和 c 两个chunks:
// a.html页面 htmlWebpackPlugin.files 取到的值
{
"publicPath":"http://cdn/",
"chunks":{
"index":{
"size":154,
"entry":"http://cdn/index-6ab81e3153cfd7fb16c1.js",
"hash":"630b3fd517102652c275",
"css":[]
},
"c":{
"size":35,
"entry":"http://cdn/c-6ab81e3153cfd7fb16c1.js",
"hash":"a172ef412ae0af3b89b9",
"css":[]
}
},
"js":["http://cdn/index-6ab81e3153cfd7fb16c1.js", "http://cdn/c-6ab81e3153cfd7fb16c1.js"],
"css":[]
}
最后,通过.source()拿到chuankName 为 index的 js代码,直接插入 <script>
标签中。而外链方式的脚本直接通过for循环htmlWebpackPlugin.files.chunks
,排除chuankName为index的chunk就可以了。
下图为最终生成 的a.html(inline的script内容过长,已折叠):
四、 loader的配置和使用
webpack本身只能打包Javascript文件,对于其他资源例如 css,图片,或者其他的语法集比如jsx,是没有办法加载的。 这就需要对应的loader将资源转化后再加载进来。
(1)css loader
// 文件目录
webpack-demo
|- app.js
|- webpack.config.js
|- package.json
|- src
|- style
|- base.css
|- common.css
app.js中引入base.css:
// src/app.js
import "./style/base.css"
base.css内容(其中又引入了common.css):
// src/style/base.css
@import "./common.css";
html{
background: yellow;
}
.box1{
display: flex;
}
common.css内容:
// src/style/common.css
.box2{
display: flex;
}
由于css中包含了一些可能导致浏览器样式差异的属性,除了安装依赖css-loader
和style-loader
外,通常还需要安装postcss-loader
和 autoprefixer
,用于自动补充css前缀。
// 安装 postcss-loader 和 autoprefixer
npm install postcss-loader autoprefixer --save-dev
webpack.config.js配置如下:
const path = require('path');
var htmlWebpackPlugin = require("html-webpack-plugin")
module.exports = {
entry: "./src/app.js",
output: {
path: path.resolve(__dirname, "dist"),
filename: "bundle.js",
},
plugins: [
new htmlWebpackPlugin({
filename: "index.html",
inject: "body",
})
],
module: {
rules: [
{
test: /\.css$/,
use:["style-loader","css-loader",{
loader: 'postcss-loader',
options: {
plugins: [
require("autoprefixer")({browsers: ["last 2 versions"]})
]
}
}],
},
]
}
}
打包完成后审查元素,发现base.css中的flex已自动加上前缀,但是通过@import
方式引入的common.css中的flex并未加上前缀,如下图所示:
解决方法,给css-loader添加额外配置 importLoaders
:
// webpack.config.js
module.exports = {
...
module: {
rules: [
{
test: /\.css$/,
use:["style-loader",
{
loader: 'css-loader',
options: {
importLoaders: 1, // 表示在css-loader处理之前,指定相应数量的loader来先处理import进来的资源,即需要先经过postcss-loader的处理
}
},{
loader: 'postcss-loader',
options: {
plugins: [
require("autoprefixer")({browsers: ["last 2 versions"]})
]
}
}],
},
],
}
}
若整个项目中使用的都是预编译sass或less,则这里css-loader 不需要配置importLoaders=1,因为less和sass都会自动补全前缀,接下来会讲到。
(2)less 和sass loader
安装less-loader
,如果没有less, 还需要安装less:
// 安装less-loader
npm install less-loader --save-dev
// 如果用的是sass,则安装sass-loader
// npm install sass-loader --save-dev
app.js中引入less文件:
// app.js
import "./style/cover.less" // 或者使用 require ("./style/cover.less")
webpack.config.js中相应配置即可:
// webpack.config.js
module.exports = {
...
module: {
rules: [
{
test: /\.less$/,
// 由于less会自动补全前缀,故而这里的 css-loader 不需要配置importLoaders参数
use:["style-loader","css-loader",{
loader: 'postcss-loader',
options: {
plugins: [
require("autoprefixer")({browsers: ["last 2 versions"]})
]
}
},"less-loader"], // 若用的是sass, 这里对应修改为sass-loader
},
],
}
}
(3)处理模板文件
这里只讲html和ejs模板文件的处理,更为详细的可移步这里。
1. html-loader
// 安装 html-loader
npm install html-loader --save-dev
模板文件 test.html:
<!-- test.html-->
<div>
This is test.html
</div>
app.js:
// app.js
import layer from "./xxx/test.html"
document.getElementById("app")
app.innerHTML=layer
// console.log(layer)
webpack.config.js:
// webpack.config.js
module.exports = {
...
module: {
rules: [
{
test: /\.html$/,
use:["html-loader"],
},
],
}
}
打包结果如下图所示,可见html-loader将html资源处理成了字符串。
2. ejs-loader
模板文件 test2.ejs:
// 模板文件 test2.ejs
<div class="test2">
<div>this is <%= name %></div>
<% for(var i=0;i<arr.length;i++){ %>
<%= arr[i] %>
<% } %>
</div>
layer.js:
// layer.js
import tpl2 from "../xxx/test2.ejs"
export default function () {
return {
name:"Test",
tpl:tpl2
}
}
app.js:
// app.js
import Layer from "./js/layer"
var app=document.getElementById("app")
var layer=new Layer()
app.innerHTML=layer.tpl(
{
name:"ejs",
arr:["apple","banana","pear"]
}
)
// console.log(layer)
webpack.config.js
// webpack.config.js
module.exports = {
...
module: {
rules: [
{
test: /\.ejs$/,
use:["ejs-loader"],
},
],
}
}
打包结果如下图所示: