webpack4打包传统H5多页面
1. webpack核心概念
- 入口(entry)
- 输出(output)
- loader
- 插件(plugins)
官方文档: https://www.webpackjs.com/configuration/output/#output-filename
1.1 入口
指定 webpack 由哪个模块作为项目构建的开始.通过配置 entry
属性,指定一个或多个起点,默认值 ./src
:
用法: entry: string|Array ,可以是一个字符串也可以是一个数组,还可以是一个对象。
module.exports = {// 字符串入口entry: './path/leo/file.js'
}
module.exports = {// 数组,指定多个入口 entry: ['xxx','yyyy']
}module.exports = {// 为入口命名 entry: {file: './path/leo/file.js'}
}module.exports = {entry: {// 为入口命名main: ['./path/leo/file.js', './path/leo/index.js', './path/leo/server.js']}
}
1.2 Chunk 的概念
所谓Chunk,就是在Webpack打包过程中,内部形成的代码块,代码块中的内容可能来源于一个文件中,也可能来源于多个文件中。比如入口文件 index.js ,它依赖于模块A,而模块A又依赖于模块B, 那么webpack 在打包后整个入口文件就会形成一个chunk。
如果entry 传入一个字符串或字符串数组,chunk 会被命名为
main
如果传入一个对象,则每个键(key)会是 chunk 的名称,该值描述了 chunk 的入口起点。比如
entry: './path/leo/file.js'
chunk的名称为main,entry: ['xxx','yyyy']
chunk的名称也为 main.entry: {file: './path/leo/file.js' }
那么chunk的名称就为 file。
有了chunk 的命名,html在嵌入js文件的时候,就可以指定需要嵌入的 chunk。
1.3 出口
指定 webpack 最终输出的文件输出位置和文件名等信息。默认输出位置为 ./dist
output: {path: path.resolve(__dirname, 'dist'), //输出的目录绝对路径filename: 'myjs-webpack.bundle.js' //输出的文件名称
}
// 占位符
output: {path: path.resolve(__dirname, 'dist'),filename: '[name].js'
}
更多占位符:
[hash] | 模块标识符(module identifier)的 hash |
---|---|
[chunkhash] | chunk 内容的 hash |
[name] | 模块名称 |
[id] | 模块标识符(module identifier) |
[query] |
模块的 query,例如,文件名 ? 后面的字符串
|
1.4 module与 loader
对于webpack来说,要处理的每个文件都是一个module,要想让webpack识别并处理这个module,那就需要使用 loader来进行解析。比如默认情况下 webpack只会处理 js文件,那么要处理 css文件,图片文件这些非js的文件,那就必须使用对应的 loader来解析这些文件。
module: {rules: [{ test: /\.txt$/, use: 'raw-loader' }]
}
所有的文件处理配置都应该放到 module中, 至于如何处理,那就要配置相关的规则 rules 就是指定规则,它是一个复数,表示多个规则,所以其值是一个数组。
- test 用来标识出应该被对应的 loader 进行转换的某个或多个文件,它负责筛选出文件。
- use 表示转换时要用哪个 loader
使用loader的三种方式:
- 配置(推荐):在 webpack.config.js 文件中指定 loader。
- 内联:在每个 import 语句中显式指定 loader。 本教程中会在 html文件中使用这种内联的方式
import Styles from 'style-loader!css-loader?modules!./styles.css';
- CLI:在 shell 命令中指定它们。
webpack --module-bind jade-loader --module-bind 'css=style-loader!css-loader'
loader特性:
- 能够对资源使用流水线(pipeline)。一组链式的 loader 将按照相反的顺序执行。loader 链中的第一个 loader 返回值给下一个 loader。在最后一个 loader,返回 webpack 所预期的 JavaScript。
- loader 可以是同步的,也可以是异步的。
- loader 运行在 Node.js 中,并且能够执行任何可能的操作。
- loader 接收查询参数。用于对 loader 传递配置。
- loader 也能够使用 options 对象进行配置。
- 除了使用 package.json 常见的 main 属性,还可以将普通的 npm 模块导出为 loader,做法是在 package.json 里定义一个 loader 字段。
- 插件(plugin)可以为 loader 带来更多特性。
- loader 能够产生额外的任意文件。
1.5 插件
让 webpack 能够执行更多任务,从优化和压缩,到重新定义环境中的变量,插件目的在于解决 loader 无法实现的其他事。
webpack本身内置了很多插件,也可以使用第三方插件。
使用时,只需要 require
它,并添加到 plugins
数组,通过 new
实例化即可:
// 通过 npm 安装
const HtmlWebpackPlugin = require('html-webpack-plugin');
// 导入 webpack 是为了访问内置插件,如 webpack.DefinePlugin
const webpack = require('webpack'); const config = {module: {rules: [{ test: /\.txt$/, use: 'raw-loader' }]},plugins: [new HtmlWebpackPlugin({template: './src/index.html'})]};module.exports = config;
1.6 模式
通过配置 mode
参数,指定当前的开发模式,有 development
和 production
两个值:
module.exports = {mode: 'production'
};
也可以通过 CLI 参数传递:
webpack --mode=production
development |
会将 process.env.NODE_ENV 的值设为development 。启用 NamedChunksPlugin 和 NamedModulesPlugin 。
|
---|---|
production |
会将 process.env.NODE_ENV 的值设为 production 。启用 FlagDependencyUsagePlugin , FlagIncludedChunksPlugin , ModuleConcatenationPlugin , NoEmitOnErrorsPlugin ,OccurrenceOrderPlugin , SideEffectsFlagPlugin 和 UglifyJsPlugin 。
|
process 是 NodeJS 应用全局对象,表示进程。 启动webpack的时候其实就启动了nodeJS应用
1.7 关于配置文件
webpack 的配置文件,是导出一个对象的 JavaScript 文件,由 webpack 根据对象定义的属性进行解析。
因为 webpack 配置是标准的 Node.js CommonJS 模块,可以做到以下事情:
- 通过
require(...)
导入其他文件; - 通过
require(...)
使用npm
的工具函数; - 使用 JavaScript 控制流表达式,例如
?:
操作符; - 对常用值使用常量或变量;
- 编写并执行函数来生成部分配置;
1.8 模块
开发中将程序分解成离散功能块,称为模块。而 webpack 模块能够以各种形式表达他们的依赖关系。
- es6:
import
语句; - CommonJS:
require()
语句; - AMD:
define
和require
语句; css/sass/less
文件中的@import
语句;- 样式(
url(...)
)或 HTML 文件(<img src=...>
)中的图片链接(image url
);
模块解析:
使用 resolver
库来找到模块的绝对路径,帮助 webpack 找到 bundle 中需要引入的模块代码,这些代码包含在每个 require
/ import
语句中,在模块打包中,webpack 使用 enhanced-resolve 来解析文件路径
- 绝对路径:
import "/home/me/file";import "C:\\Users\\me\\file";
- 相对路径
import "../src/file1";
import "./file2";
- 模块路径,将会在 node_modules 文件夹中搜索
import "module";
import "module/lib/file";
2. 项目规划
npm init -y
目录结构
├── public 存放html模板页面
│ ├── include 存放公共的html代码片段
│ │ ├── header.html
│ │ └── tail.html
│ └── index.html 首页
│ ├── about.html 关于
│ ├── favicon.ico 站点图标
├── src 存放源代码(需要编译)
│ ├── assets 存放资源,包含css文件,字体文件,图片文件
│ │ ├── css
│ │ ├── fonts
│ │ └── img
│ ├── common 公共js文件
│ └── index.js
│ ├── about.js
└── vendor 存放第三方提供的组件,src下js文件中需要 import才能使用
├── static 静态文件,这些文件将会原封不动拷贝到发布的assets目录中
├── readme.md
├── package.json 依赖的js文件,启动脚本
注意: public目录下只有html文件和 favicon.ico 文件(图标)。 每个html文件名都对应一个 src下的 同名的js文件。html文件名与js文件名一一对应(目录也要对应)。include 文件夹中是html片段,这些片段使用
<%= require('html-loader!./include/header.html')%>
的方式被包含到其它html页面中。
3. 总目标
- 可以将HTML代码片段提取到独立文件,其它HTML文件需要使用的时候,可以像其它语言一样 include 这些片段到HTML文件中。
- 每个HTML文件都对应一个js文件,这个js文件作为 webpack的入口文件,被webpack处理后自动引入到HTML中。
- 可以使用sass语法编写css,即编写 scss文件,这些文件被webpack处理后,形成独立的css文件,并自动引入到HTML中。
- HTML,CSS中的图片被webpack处理后,放到独立文件夹中。
- 开发的时候启动一个Web服务器方便开发,能够跨域访问后端接口数据。
- JS中可以使用ES6 语法
- 使用 ESLint 统一JS代码风格。
4. HTML页面内容准备
4.1 公共HTML片段文件
这里以简单的页头和页尾为例:
public/include/header.html
<div>这是头部分<a href="/production/index.html">产品中心</a><a href="/about.html">关于我们</a>
</div>
public/include/tail.html
<div>这是页脚部分
</div>
4.1 首页
在首页中嵌入页头和页脚, <%= xxxx %>
这种写法浏览器是无法识别的,需要通过webpack处理后才能被浏览器识别
public/index.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>这是首页</title>
</head>
<body><%= require('html-loader!./include/header.html')%><div class="box">首页内容</div><%= require('html-loader!./include/tail.html')%>
</body>
</html>
public/index.html 对应的入口文件为 src/index.js 。 这里使用了jQuery 库,输出简单的内容
import $ from 'jquery'
$(() => {alert("这是index.js");
})
4.2 关于页
public/about.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>关于我们</title>
</head>
<body><%= require('html-loader!./include/header.html')%><div>关于我们</div><%= require('html-loader!./include/tail.html')%>
</body>
</html>
public/about.html 对应的入口文件为 src/about.js :
import $ from 'jquery'
$(() => {alert("about.js");
})
4.2 二级页面-产品首页
public/production/index.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>产品中心</title>
</head>
<body><%= require('html-loader!../include/header.html')%><div>产品中心</div><%= require('html-loader!../include/tail.html')%>
</body>
</html>
public/production/index.html 对应的入口文件为 src/production/index.js
import $ from 'jquery'
$(() => {alert("production------>index.js");
})
下面,通过webpack的配置,先让这几个页面能够在浏览器中运行起来。
5. webpack配置
目前没有安装任何依赖,下面安装依赖
- webpack webpack-cli webpack依赖
- html-webpack-plugin 用于将webpack处理后的js文件,引入到HTML文件中,并输出HTML
- html-loader 用于处理 将HTML片段 合并到其它HTML中。
- webpack-dev-server 启动服务器
npm i webpack webpack-cli clean-webpack-plugin html-webpack-plugin html-loader webpack-dev-server -D
- jQuery 页面上用到了jQuery库
npm i jQuery -S
webpack.config.js
const path = require('path')
module.exports = {entry:{},output:{},resolve:{},module:{rules:[]},plugins:[]
}
5.1 webpack 入口文件
entry:{'index': './src/index.js','about': './src/about.js','production/index': './src/production/index.js'
},
入口文件的文件都使用相对路径,那么相对于哪个路径? webpack的配置中,有个 context选项配置,它是一个 绝对路径
,是一个基础目录, entry和 loader 中都是相对于这个基础路径,其默认值是:__dirname
即当前NodeJS运行的 webpack.config.js 文件所在的目录。以上的配置相当于:
context: path.resolve(__dirname),
entry:{'index': './src/index.js','about': './src/about.js','production/index': './src/production/index.js'
},
这里的三个HTML文件,每个都对应一个js入口文件,其入口命名(Chunk )就是文件相对路径去掉 ./src
和 .js
后缀 。 之所以要用这样的规则,是因为一个项目中的HTML和入口文件会很多,将来不可能每个JS入口文件都这样来手工配置,到时会使用nodejs 访问磁盘目录,按照这个规则自动生成 entry配置。
5.2 webpack 输出配置
webpack处理完毕后输出到指定的位置
output:{path: path.resolve('./dist'), //必须是绝对路径filename: '[name]-[chunkhash].js'
}
5.3 配置 html-webpack-plugin
每个入口文件都对应一个HTML,该如何生成HTML,需要在 html-webpack-plugin 对象中指明输出的 HTML文件名,HTML模板等。详细配置可以参考官方文档
每个HTML文件都应该对应一个 html-webpack-plugin 对象。 HTML文件太多,后面会使用编码的方式来自动生成配置。
...
const HtmlWebpackPlugin = require("html-webpack-plugin")
...
plugins:[new HtmlWebpackPlugin({filename: 'about.html',template: './public/about.html',favicon: './public/favicon.ico',chunks: ['about']}),new HtmlWebpackPlugin({filename: 'index.html',template: './public/index.html',favicon: './public/favicon.ico',chunks: ['index']}),new HtmlWebpackPlugin({filename: 'production/index.html',template: './public/production/index.html',favicon: './public/favicon.ico',chunks: ['production/index']}),
]
filename: 最终生成的HTML文件名称,可以使用子文件夹,将来会生成到 output 指定的文件夹中。
template: HTML模板。 我们将所有的HTML模板都放到了public目录,这个配置项默认是 ‘./src/index.ejs’
favicon: 自动向HTML模板中插入指定的 icon文件
chunks: 是一个数组,指定要引入的js文件。默认情况下会引入 webpack 输出的 js。比如上面有三个输出,如果不配置chunks,那么 index.js , abount.js 和 production/index.js 都会被引入到 HTML文件中。
5.4 package.json配置启动命令
"scripts": {"build": "npx webpack --config webpack.config.js --mode production","dev": "npx webpack-dev-server --config webpack.config.js --color --inline --hot --progress --mode development --port 8080 --open"}
- build 构建生产环境输出 npx是执行Node软件包的工具,它从 npm5.2版本开始,就与npm捆绑在一起。它会寻找 node_modules 下可执行包然后执行。如上面要执行的 webpack命令是存放在 node_modules中的。
- –config 指定webpack的配置文件
- – mode 指定是 production 还是 development ,这样可以使用
process.env.NODE_ENV
来判断是哪个环境。 - dev 启动web服务。 它是使用 webpack-dev-server 插件来运行webpack的 --port 指定了启动端口为 8080。–open 会自动打开浏览器。 这些配置参数可以写到 webpack.config.js 中,这里暂时在命令行中配置,后面再移植到 webpack.config.js 中。详细配置,参考官方文档
5.4.1 启动开发环境
npm run dev
启动后,会自动打开浏览器,访问 “http://localhost:8080”
webpack-dev-server 启动后会将 webpack 的输出结果映射到内存中,我们在磁盘上看不到webapck输出的结果。
查看首页 http://localhost:8080/index.html 源码:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>这是首页</title>
<link rel="icon" href="favicon.ico"></head>
<body><div>这是头部分<a href="/index.html">首页</a><a href="/production/index.html">产品中心</a><a href="/about.html">关于我们</a>
</div><div class="box">首页内容</div><div>这是页脚部分
</div>
<script src="index-be16bdb4c4cb8f5ff984.js"></script></body>
</html>
再对照 public/index.html模板:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>这是首页</title>
</head>
<body><%= require('html-loader!./include/header.html')%><div class="box">首页内容</div><%= require('html-loader!./include/tail.html')%>
</body>
</html>
可以看到, 根据 模板生成的HTML做了如下改变:
- 插入了
<link rel="icon" href="favicon.ico">
<%= xxx %>
的内容被替换成了header.html 和 tail.html 中的HTML 片段- 插入了 webpack 输出的js文件。
5.4.2 启动编译
npm run build
dist 文件夹中的内容
│ about-1c72cf882218d6c9ef0b.js
│ about.html
│ favicon.ico
│ index-0af568ee344bf0082880.js
│ index.html
└─productionindex-0d30eb18d3678e852376.jsindex.html
6. 优化出口配置
上面输出的html文件和 js文件都在同一个目录中,通过配置,可以将 js输出到指定的 assets/js 文件夹中,只需要修改 output.filename项,filename项上是可以带文件夹的。
output:{path: path.resolve('./dist'),filename: 'assets/js/[name]-[hash].js' //存放到path指定的目录下
}
再次 npm run build
后的结果:
│ about.html
│ favicon.ico
│ index.html
├─assets
│ └─js
│ │ about-1c72cf882218d6c9ef0b.js
│ │ index-0af568ee344bf0082880.js
│ │
│ └─production
│ index-0d30eb18d3678e852376.js
│
└─productionindex.html
7. 删除上次编译结果
每次 npm run build
的时候,webpack不会清理 dist目录,因为只要文件有改动,那么生成的文件内容的Hash值都不一样,所以上一次编译的结果被保留了下来造成了混乱。配置 clean-webpack-plugin 插件可以每次build之前清理 dist目录
npm i clean-webpack-plugin -D
webpack.config.js
...
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
...
plugins:[new CleanWebpackPlugin()),...
]
注意: CleanWebpackPlugin 要写在 HtmlWebpackPlugin 插件的前面
8. 使用scss
下面使用Sass编写一个css样式文件,样式文件写到 src/assets/css文件夹中
├─assets
│ ├─css
│ │ index.scss
│ │
│ └─img
│ bg.png
src/assets/css/index.scss 内容:
$bg-color: #ee3;
.box{background-color: $bg-color;display: flex;height: 500px;width: 600px;
}
这里定义了一个类样式,如果要在 public/index.html 中使用,那么首先要在 public/index.html 对应的 src/index.js 入口文件中使用 import 导入css文件
src/index.js
...
import './assets/css/index.scss'
...
然后在 public/index.html 中使用类样式:
...
<div class="box"></div>
...
现在执行 npm run dev
会发现报错了。因为 我们在src/index.js 中导入了 index.scss 文件,这个文件 webpack是无法解析的。所以这就需要配置 loader 来解析 scss文件或者css。
8.1 安装loader
npm set SASS_BINARY_SITE http://npm.taobao.org/mirrors/node-sass
npm i style-loader css-loader node-sass sass-loader -D
因为 sass-loader 依赖于 node-sass . 使用之前先设置 node-sass 环境变量,加速安装包下载
8.2 配置loader
module:{rules:[{//它会应用到 .css .scss .sass 后缀的文件,//use数组loader的名字是有顺序的,即先由sass-loader,再由css-loader处理,最后由style-loader处理test: /\.(sc|c|sa)ss$/,use: ['style-loader', 'css-loader', 'sass-loader']}]
}
- 首先是 sass-loader 加载 .scss 文件做转换工作,因为.scss 文件中的内容无法直接被浏览器直接识别。
- scss 转换成 css代码后,再由 css-loader 来加载css代码
- 最终加载的css代码被包裹到
<style>...</style>
中,从而插入到 html文件中。
现在再次 npm run dev
,服务启动后发现css生效了。
仔细观察发现css代码最终被放到了 html中的 style标签中。
8.3 css中使用图片文件
src/assets/css/index.scss 中为box 类样式添加一个背景图片:
$bg-color: #ee3;
.box{
...background: url(../img/bg.png);
...
}
执行 npm run dev
后发现报错。 原因是 css-loader 在加载 css 的时候,遇到了 url所指向的图片,图片这种资源 scss-loader 是无法处理的,因为图片文件需要被拷贝到最后的输出目录中,css中的url指向的路径也发生了改变。
webpack通过file-loader处理资源文件,它会将rules规则命中的资源文件按照配置的信息(路径,名称等)输出到指定目录,并返回其资源定位地址,默认的输出名是以原文件内容计算的MD5 Hash命名的。官方文档:https://www.webpackjs.com/loaders/file-loader/
npm i file-loader -D
webpack.config.js 文件配置 loader 规则:
rules:[
...
{test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, //匹配图片文件loader: 'file-loader',options: {outputPath: 'assets/img' //将图片输出到 dist/assets/img文件夹下}
}]
再次运行npm run dev
在开发者工具中查看:
这说明 file-loader 把 src/assets/img/bg.png 这个文件拷贝到了 dist/assets/img/992ac5d15ba3b012bb054bf80016a636.png ,即文件名已经变成了 992ac5d15ba3b012bb054bf80016a636.png , 然后返回了此时文件的路径。
可以运行 npm run build
来看看最终的编译结果:
├─assets
│ ├─img
│ │ 992ac5d15ba3b012bb054bf80016a636.png
│ │
│ └─js
│ │ about-83083e244833fcde3bfe.js
│ │ index-83083e244833fcde3bfe.js
│ │
│ └─production
│ index-83083e244833fcde3bfe.js
## 8.4 生成css文件
上面虽然能正常使用css了,但是我们发现最终生成的结果并没有css代码,那么这些css代码哪里去了?为什么运行的时候又没有问题?
因为 css代码是在 src/index.js 中 import的, webpack借助 相关的loader (‘style-loader’, ‘css-loader’, ‘sass-loader’) 将这些css代码全部处理成了字符串,编译到了js文件中了。可以打开 index-xxxxxx.js 这个文件,然后搜索 .box
发现那些css确实被处理成了javascript 字符串
js文件被浏览器加载后,js代码开始执行,此时它会在 html文档的head 节点下添加一个 style节点,然后将css代码输出到节点中。
那如何才能将这些css代码"提取" 出来?webpack4中使用 mini-css-extract-plugin 官方文档 https://webpack.js.org/plugins/mini-css-extract-plugin/#minimizing-for-production 来提取css样式, 使用 optimize-css-assets-webpack-plugin 来压缩css
npm i mini-css-extract-plugin optimize-css-assets-webpack-plugin -D
webpack.config.js
...
const MiniCssExtractPlugin = require("mini-css-extract-plugin");//提取css到单独文件的插件
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');//压缩css插件
...module:{rules:[{// 将 style-loader 替换成 MiniCssExtractPlugin.loader// 因为最后一步不是生成 style标签,而是提取出单独的文件。test: /\.(sc|c|sa)ss$/,use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader']}, ...]},
...plugins:[
...
new MiniCssExtractPlugin({filename: "assets/css/[name]_[hash].css"//输出目录与文件}),
new OptimizeCssAssetsPlugin()
]
需要注意的是: MiniCssExtractPlugin 还需要在 loader上配置一下,MiniCssExtractPlugin 提供了一个loader,在module.rules.use 组数的第一个元素位置上配上这个loader表示文件处理的最后一步交给 这个loader来处理。一定要注意顺序。plugins中配置了插件,并指定了提取的css文件的存储路径和文件名。[name]是入口文件的name。
编译后的结果:
├─assets
│ ├─css
│ │ index_f9c026e959b5b2b1b2d1.css
│ ├─img
│ │ 992ac5d15ba3b012bb054bf80016a636.png
│ └─js
│ │ about-f9c026e959b5b2b1b2d1.js
│ │ index-f9c026e959b5b2b1b2d1.js
│ └─production
│ index-f9c026e959b5b2b1b2d1.js
在 index.html文件中, .css 文件使用了 link标签 引入了 css文件。
现在执行 npm run dev
发现图片无法加载:
Failed to load resource: the server responded with a status of 404 (Not Found)
查看生成的 css文件,发现 .box 类变成:
index_f9c026e959b5b2b1b2d1.css
.box {background-color: #ee3;display: flex;background: url(assets/img/992ac5d15ba3b012bb054bf80016a636.png);height: 500px;width: 600px
}
这个css文件 位于 assets/css 文件夹中,而 url中使用的是一个相对地址
在css 文件中 ,url 如果使用的是相对地址,则是基于当前css文件的
也就是说 background实际加载的是 assets/css/assets/img/xxxx.png 这个路径显然是错误的。那怎么解决?
webpack 配置的 output.publicPath 可以解决。 它的意思是在输出的时候,在所有的路径前面都添加一个前缀,比如: http:// xxx.com/ ,如果将其设置为"/" 那输出的时候所欲的路径都会从服务器的根目录开始。
所以在 webpack.config.js 中对 output.publicPath 进行配置:
output:{path: path.resolve('./dist'),filename: 'assets/js/[name]-[hash].js',publicPath: '/'}
再次 'npm run dev '就发现正常了。此时在查看css文件,发现是这样的:
.box {background-color: #ee3;display: flex;background: url(/assets/img/992ac5d15ba3b012bb054bf80016a636.png);height: 500px;width: 600px
}
因为url 是一个绝对路径,是从服务器的根目录开始的,所以这个路径就是正确的。
9. 使用bootstrap
bootstrap 是常用的css样式库,使用之前先安装:
npm i bootstrap -S
不要直接在 html模板文件中引入,而是在 js文件中引入。 webpack 处理是从 js文件入口开始处理的,而不是 HTML文件。
在 src/index.js 中引入 bootstrap样式
import $ from 'jquery'
import './assets/css/index.scss'
import 'bootstrap/dist/css/bootstrap.css'
$(() => {alert("index.js======");
})
在 public/index.html 模板中使用
...
<button class="btn btn-success">一个按钮</button>
...
现在 npm run dev
,bootstrap样式就生效了
9.1 问题
因为在 js中引入了 自己的 assets/css/index.scss 和 bootstrap.css ,最终自己的css和 bootstrap的css文件被打包到了最终生成的css文件中了。如果其它js中也使用bootstrap,那就会又生成一份css文件。也就是说 boostrap库的css会不断的重复生成,这样会影响最终的性能。
为了减小css文件大小,需要将库css也分离出来,因为库css一般不会发生变化,而自己写的css会变,所以需要将 bootstrap库的css独立出来存放到单独的文件中。
9.2 分离库文件
库文件有 js库和 css库,这些都需要分离出来。webpack 4+ 版本使用内置的 SplitChunksPlugin 插件来进行公共部分的提取。因为 SplitChunksPlugin 是 webpack 4+ 版本内置的插件, 所以无需安装, 只需在 webpack.config.js 中配置:
plugins:[...
],
// 提取公共模块,包括第三方库和自定义工具库等
optimization: {// 找到chunk中共享的模块,取出来生成单独的chunksplitChunks: {chunks: "all", // async表示抽取异步模块,all表示对所有模块生效,initial表示对同步模块生效cacheGroups: {vendors: { // 抽离第三方插件test: /[\\/]node_modules[\\/]/, // 指定是node_modules下的第三方包name: "vendors",priority: -10 // 抽取优先级},utilCommon: { // 抽离自定义的公共库name: "common",minSize: 0, // 将引用模块分离成新代码文件的最小体积minChunks: 2, // 表示将引用模块如不同文件引用了多少次,才能分离生成新chunkpriority: -20}}},// 为 webpack 运行时代码创建单独的chunkruntimeChunk:{name:'manifest'}
}
SplitChunksPlugin 是在 配置文件 optimization 选项来配置的,我们需要抽离的公共部分有如下:
- 库 css或 js
- 本项目的公共样式或js
- webpack 运行时代码
9.2.1 库css或js
本项目中用到了 jQuery 库 和 bootstrap库, 库文件都是在项目的 node_modules 中的,所以分离这部分代码的标准很简单,只要 chunk(代码片段) 是从 node_modules 来的,就是库代码,此时就把这些代码都抽离出来合并在一起,取名为 venders(提供者)
vendors: { // 抽离第三方插件test: /[\\/]node_modules[\\/]/, // 指定是node_modules下的第三方包name: "vendors",priority: -10 // 抽取优先级
},
9.2.2 本项目的公共样式或js
webpack在运行的过程中,如果发现某些chunk被引用了超过2次,比如 某个css或者 js被其它文件 import 超过了2次,那就将这些凡是超过2次的代码合并在一起,起名为 common,那么如果是css,那么将来打包后就独立成 common.css ,js 就是 common.js
utilCommon: { // 抽离自定义的公共库name: "common",minSize: 0, // 将引用模块分离成新代码文件的最小体积minChunks: 2, // 表示将引用模块如不同文件引用了多少次,才能分离生成新chunkpriority: -20
}
9.2.3 webapck运行时代码
最终生成的js代码中都是有webpack运行时代码的,这部分代码也不会变化,也应该分离出来
// 为 webpack 运行时代码创建单独的chunk
runtimeChunk:{name:'manifest'
}
打包后的结果,dist/assets/ 目录
├─css
│ index_aafcf62739bb7ef43cdb.css
│ vendors_aafcf62739bb7ef43cdb.css
├─img
│ 992ac5d15ba3b012bb054bf80016a636.png
└─js│ about-aafcf62739bb7ef43cdb.js│ index-aafcf62739bb7ef43cdb.js│ manifest-aafcf62739bb7ef43cdb.js│ vendors-aafcf62739bb7ef43cdb.js└─productionindex-aafcf62739bb7ef43cdb.js
boostrap 代码被分离到了 vendors_aafcf62739bb7ef43cdb.css 文件中。
jquery 代码被分离到了 vendors-aafcf62739bb7ef43cdb.js 文件中
webpack运行时代码分离到了 vendors-aafcf62739bb7ef43cdb.js 文件中。
index.html文件中引入情况:
...<head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width,initial-scale=1" /><meta http-equiv="X-UA-Compatible" content="ie=edge" /><title>这是首页</title><link rel="icon" href="/favicon.ico" /><link href="/assets/css/vendors_aafcf62739bb7ef43cdb.css" rel="stylesheet"/><link href="/assets/css/index_aafcf62739bb7ef43cdb.css" rel="stylesheet" /></head>
...<body>...<script src="/assets/js/manifest-aafcf62739bb7ef43cdb.js"></script><script src="/assets/js/vendors-aafcf62739bb7ef43cdb.js"></script><script src="/assets/js/index-aafcf62739bb7ef43cdb.js"></script></body>
10. 处理HTML中的 img 图片
现在在 src/assets/img 文件夹中添加一个图片 nodejs.jpg
文件, 然后在 public/index.html中引入这个图片:
├─public
│ index.html
├─src
│ └─assets
│ └─img
│ nodejs.jpg
public/index.html
...
<img src="../src/assets/img/nodejs.jpg" />
...
然后 npm run build
之后发现 nodejs.jpg 这个图片并没有被处理到 dist/assets/img 目录中, 生成的 index.html 文件中 依然还是:
<img src="../src/assets/img/nodejs.jpg" />
没有做任何改变,生成的 img src 明显是错误的。
我们预期的结果应该是这样的:
- src/assets/img/nodejs.jpg 文件被file-loader 处理,计算出文件的md5 Hash值,假设为 123abcde456, 然后将文件拷贝到 dist/assets/img/123abcde456.jpg
- file-loader 返回图片文件的路径为 /assets/img/123abcde456.jpg
- 替换掉 img 标签的src 为 /assets/img/123abcde456.jpg , 即
<img src="/assets/img/123abcde456.jpg" />
如何才能正确处理img标签中的文件?在 html文件中使用 ejs语法
10.1 html中使用ejs语法
HtmlWebpackPlugin 在处理HTML模板的时候,是支持 ejs语法的,如 <%= xxxxxx%>
,前面将公共的头和尾嵌入到页面中就是使用了 ejs语法,借助于 html-loader 完成了html 内容的加载
<%= require('html-loader!./include/header.html')%>
现在需要让webpack能够正确的处理 html中的img,也可以使用 require语句来加载图片,写法如下:
<img src="<%=require('../src/assets/img/nodejs.jpg').default%>" />
注意后面有一个 .default
10.2 进一步优化图片处理
项目中有些时候会使用一些小的图片文件,这些小的图片文件可以直接被处理成 base64编码,放入到 img标签的src中或者 css文件中。 浏览器在渲染图片文件的时候,会向服务器发起请求将图片下载到本地,如果这样的小图片太多,那么浏览器会发起很多次请求,对性能造成影响。如果直接将图片base64文本嵌入到网页中,浏览器就可以直接解析,无需向服务器请求图片。
可以用 url-loader 来做这个优化
npm i url-loader -D
webpack.config.js
module:{rules:[...,{test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, //匹配图片文件loader: 'url-loader',options: {limit:10*1024,//小于limit限制的图片将转为base64嵌入引用位置, 单位为字节(byte)fallback:'file-loader',//大于limit限制的将转交给指定的file-loader处理outputPath:'assets/img'//传入file-loader将图片输出到 dist/assets/img文件夹下}}]
},
11 .媒体文件/字体文件处理
媒体文件,如音频,视频以及 css样式中的用到的字体文件,同样需要webpack在编译的时候处理这些资源。这些资源统一使用 url-loader来处理
webpack.config.js
module:{rules:[...,{test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,loader: 'url-loader',options: {limit: 10000,name: 'assets/media/[name].[hash:7].[ext]'}},{test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,loader: 'url-loader',options: {limit: 10000,name: 'assets/fonts/[name].[hash:7].[ext]'}}]
},
12. 动态配置entry 与 HtmlWebpackPlugin
对于多页面的H5 HTML页面很多,每添加一个HMTL文件,就对应有一个js 文件,这样就需要在 webpack.config.js 中不停的添加 entry 与 HtmlWebpackPlugin 对象。我们可以按照 public目录下的HTML模板的组织结构和 src下的 js文件组织结构,动态的生成配置,只需要遍历目录下的文件即可。
完整的 webpack.config.js 文件内容如下:
const path = require('path')
const glob = require("glob")
const HtmlWebpackPlugin = require("html-webpack-plugin")
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const MiniCssExtractPlugin = require("mini-css-extract-plugin");//提取css到单独文件的插件
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');//压缩css插件// HTML模板文件所在的文件夹
const htmlDir = path.join(__dirname, 'public/')
// 入口文件所在文件夹
const srcDir = path.join(__dirname, 'src/')/** 扫描获取入口 */
function scanEntry() {var entry = {}glob.sync(srcDir + '/**/*.js').forEach(name => {name=path.normalize(name)// 如 index // about// production/indexchunkName=name.replace(srcDir, '').replace(/\\/g,'/').replace('.js', '')entry[chunkName] = name})return entry
}
/** 扫描获取所有HTML模板 */
function scnanHtmlTemplate() {var htmlEntry = {}// 扫描目录以及子目录下所有html结尾的文件,不包含 include 文件夹glob.sync(htmlDir + '/**/*.html', {ignore: '**/include/**'}).forEach(name => {name=path.normalize(name)chunkName=name.replace(htmlDir, '').replace(/\\/g,'/').replace('.html', '')htmlEntry[chunkName] = name})return htmlEntry
}
/** 构建HtmlWebpackPlugin 对象 */
function buildHtmlWebpackPlugins() {var tpl = scnanHtmlTemplate()var chunkFilenames = Object.keys(tpl)return chunkFilenames.map(item => {var conf = {// 如 index.html// about.html// production/index.htmlfilename: item + ".html",template: tpl[item],inject: true,favicon: path.resolve('./public/favicon.ico'),chunks: [item]}return new HtmlWebpackPlugin(conf)})
}
// 所有入口文件
const entry = scanEntry()
// 插件对象
let plugins= [new CleanWebpackPlugin({verbose: true,}),new MiniCssExtractPlugin({filename: "assets/css/[name]_[chunkhash].css"//输出目录与文件}),new OptimizeCssAssetsPlugin()
]
// 所有 HtmlWebpackPlugin插件
plugins=plugins.concat(buildHtmlWebpackPlugins())module.exports = {entry,output:{path: path.resolve('./dist'),filename: process.env.NODE_ENV==='production'? 'assets/js/[name].[chunkhash].js':'assets/js/[name].js',publicPath: '/'},resolve:{},module:{rules:[{//它会应用到普通的 `.css` 文件,//use数组loader的名字是有顺序的,即先由sass-loader,再由css-loader处理,最后由style-loader处理test: /\.(sc|c|sa)ss$/,use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader']},{test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, //匹配图片文件loader: 'url-loader',options: {limit:10*1024,//小于limit限制的图片将转为base64嵌入引用位置fallback:'file-loader',//大于limit限制的将转交给指定的file-loader处理//outputPath:'assets/img'//传入file-loader将图片输出到 dist/assets/img文件夹下name: 'assets/img/[name].[hash:7].[ext]'}},{test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,loader: 'url-loader',options: {limit: 10000,name: 'assets/media/[name].[hash:7].[ext]'}},{test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,loader: 'url-loader',options: {limit: 10000,name: 'assets/fonts/[name].[hash:7].[ext]'}}]},plugins,// 提取公共模块,包括第三方库和自定义工具库等optimization: {// 找到chunk中共享的模块,取出来生成单独的chunksplitChunks: {chunks: "all", // async表示抽取异步模块,all表示对所有模块生效,initial表示对同步模块生效cacheGroups: {vendors: { // 抽离第三方插件test: /[\\/]node_modules[\\/]/, // 指定是node_modules下的第三方包name: "vendors",priority: -10 // 抽取优先级},utilCommon: { // 抽离自定义的公共库name: "common",minSize: 0, // 将引用模块分离成新代码文件的最小体积minChunks: 2, // 表示将引用模块如不同文件引用了多少次,才能分离生成新chunkpriority: -20}}},// 为 webpack 运行时代码创建单独的chunkruntimeChunk:{name:'manifest'}}
}
13 .简化控制台输出
webpack编译或运行的时候,控制台输出内容较多,可以通过一些开关来控制,详细信息参考官方文档
module.exports= {...stats: {assets:false,modules:false,entrypoints:false}
...
}
14 .支持ES6语法
如今 ES6 语法在开发中已经非常普及,甚至也有许多开发人员用上了 ES7 或 ES8 语法。然而,浏览器对这些高级语法的支持性并不是非常好。因此为了让我们的新语法能在浏览器中都能顺利运行,就需要进行转换。Babel 这个工具就可以完成这样的转换。
14.1 安装Babel
因为Babel只是辅助工具,所以安装的时候加入 -D 参数, 这里安装的是 Babel 7 版本
npm i -D babel-loader @babel/core @babel/preset-env
14.2 配置webpack loader
因为webpack编译打包的时候是不知道 Babel存在的,而转换的工作是有Babel完成的,这就需要让webpack 将打包的js文件交给 Babel 完成转换。
在webpack中对于文件的处理,是由 loader来完成的,前面安装Babel的时候,就安装了一个 babel-loader ,将它配置到webpack中就可以做转换了。
webpack.config.js
module: {rules: [{test: /\.js$/,loader: 'babel-loader',exclude: /node_modules/}]
},
14.3 Babel的配置文件
虽然在webpack中配置了loader,但是Babel此时还不能发挥它的作用,需要在项目根目录下创建一个 .babelrc 文件(文件名前面有一个 .),内容如下:
.babelrc
{"presets": ["@babel/preset-env"]
}
15 配置别名与默认扩展名
在webpack的 resolve 选项可以配置 require 或者 import 模块时默认的扩展名和别名
@ 表示 src文件夹
resolve:{ extensions: ['.js', '.json','.scss','.css'],alias: {'@': path.resolve('./src')}},
在js文件中,如果要 import 一个 .scss文件,原来是这样:
import './assets/css/index.scss'
因为配置可扩展名,和别名,现在可以这样用:
import '@/assets/css/index'
16 配置ESLint
ESLint 是个代码检查工具,它可以配置在开发环境中,提供编码规范,帮助我们找出项目中不符合编码规范规则的代码并给出提示,告诉我们哪一个文件哪一行代码不符合哪一条编码规范,方便你去修改代码。在VS Code开发工具中,可以配置在文件保存的时候,自动启用 ESLint 代码检查,并按照规则自动修复代码。
npm i eslint -D
# 初始化ESLint 配置文件
npx eslint --init
npx eslint --init
可以初始化一个 配置文件,它是一个交互式命令,会让你做一些选择:
? How would you like to use ESLint? ...To check syntax only
> To check syntax and find problems
√ How would you like to use ESLint? · style
√ What type of modules does your project use? · esm
√ Which framework does your project use? · none
√ Does your project use TypeScript? · No / Yes
√ Where does your code run? · browser
√ How would you like to define a style for your project? · guide
√ Which style guide do you want to follow? · standard
√ What format do you want your config file to be in? · JavaScript
运行成功之后,会自动生成 .eslintrc.js 文件,这就是ESLint的配置文件。
webpack 此时并不知道 ESLint的存在 ,需要 安装 ESLint loader, 这样webpack在工作的时候才会启动 ESLint检查。
npm i eslint-loader eslint-friendly-formatter -D
webpack.config.js 中配置 ESLint Loader:
module: {rules: [..., {test: /\.js$/,loader: 'eslint-loader',enforce: 'pre',include: [path.resolve(__dirname, 'src')], // 指定检查的目录options: { // 这里的配置项参数将会被传递到 eslint 的 CLIEngineformatter: require('eslint-friendly-formatter') // 指定错误报告的格式规范}},...]},
VS Code 中安装 ESLint 插件, Settings.json 建议配置:
"files.autoSave": "off","eslint.validate": ["javascript","javascriptreact","vue"],"eslint.run": "onSave","editor.codeActionsOnSave": {"source.fixAll.eslint": true},"[html]": {"editor.defaultFormatter": "esbenp.prettier-vscode"}
webpack4打包传统H5多页面相关推荐
- Android Studio 打包H5网址页面,封装APK
Android Studio 打包H5网址页面,封装APK 一.下载 AndroidStudio 二.配置SDK 三.新建项目 四.配置项目 MainActivity WebViewClient An ...
- webpack4打包实战
一.模块化打包工具的由来 ES Module存在环境兼容问题,通过模块化方式划分的模块较多,网络请求频繁,在前端应用开发中不仅仅需要JavaScript代码需要模块化,随着应用的日益复杂,html,c ...
- 详细解剖大型H5单页面应用的核心技术点
阐述下项目 Xut.js 开发中一个比较核心的优化技术点,这是一套平台代码,并非某一个插件功能或者框架可以直接拿来使用,核心代码大概是6万行左右(不包含任何插件) .这也并非一个开源项目,不能商业使用 ...
- LAYA和TypeScript制作H5入门——页面制作
上一篇: LAYA和TypeScript制作H5入门--准备工作 一般情况下,一个H5是由多个相互关联的ui页面串联组成.遵循的大致逻辑一般是加载页面,开始页面,内容页面和结束页面.这些界面承担了用户 ...
- uniapp开发,打包成H5部署到服务器
哈喽大家好~我是马小跳.一名进阶中的程序媛. 在这里记录下自己成长的每一次进步,希望遇到志同道合的猿友 一起努力,一起把技术up up up!!! 前端使用uniapp开发项目完成后,需要将页面打包, ...
- uniapp的打包:h5、微信小程序以及APP方式
uniapp的打包:h5.微信小程序以及APP方式 H5打包 微信小程序打包 App打包 本人用的是HBuilder编译器,学习uniapp时b站某位大大推荐的,我刚开始接触代码时候也用过,那时候并不 ...
- uniapp-怎么把项目打包成H5
如果没有项目的话就自己创建项目uni-app 创建完项目我们会得到一个uniapp框架,可以往里面添加属于自己的功能和需求 但我们把需求和功能都写好,就是发布的时候,通过uniapp 我们可以发布成 ...
- 将uniapp打包成h5放在安卓webview中(解决uniapp引入第三方地图卡顿问题)
本来是使用uniapp进行开发,然后打包成安卓软件的,因为是用了地图模块(基于天地图),因为uniapp框架的限制,只能使用webview组件引入地图文件,然后出现一个问题,发现地图在浏览器中打开很流 ...
- mustache.js html模板,js模板引擎Mustache将h5模板页面转化为小程序页面
研究目的 怎么让小程序页面可以通过后台h5模板进行管理 Mustache 简单语法示例 1.{{keyName}} var data = { "name": "zhang ...
最新文章
- 技术人的不惑之路...... | 每日趣闻
- laravel中的where和orwhere的源码分析
- 常用网络故障集锦,收藏备用
- python socket编程实现的简单tcp迭代server
- VSTO学习笔记(二)Excel对象模型
- 2019/12/11学习内容摘要(Linux系统用户与用户组管理①)
- vagrant使用_使用Vagrant的初学者指南
- Python_base_正则表达式
- 用AtomicStampedReference解决ABA问题
- Studio 3T for MongoDB 破解教程
- nuke linux 插件,NUKE插件-Pos工具包V1.2
- springboot的web进阶知识(2)
- iptables实现网卡包的转发
- 自然辩证法论文 计算机,自然辩证法对人工智能学的指导意义
- PHP 与 JSP 比较(PHP、ASP、JSP是什么)
- msxml3.dll 错误 '800c0005'具体解决办法详解
- 电脑上怎么绘制流程图以及在线绘制方法
- 为什么跑椭圆机比跑步更累
- recover database
- 前端开发:Vue中v-if和v-show的使用以及应用场景
热门文章
- 【DBA充电宝】和【Python充电宝】DBA充电宝和Python充电宝微信群已建立,可加我微信(lhrbestxh)入群...
- 【重识前端】深入内存世界
- Hi3518E开发板上添加RT5370模块
- mysql 姓刘或姓李_案例-多表查询、子查询实例02(有答案)
- 【Golang | gRPC】gRPC-Client Streaming客户端流实战
- sandboxie游戏不能运行在虚拟环境中如何解决_软件测试中的虚拟化
- 云服务器系统能改win7的吗,云服务器系统能改win7的吗
- c语言汉字属于什么类型_设计源于传承|为什么字体设计太难?独属于我们的汉字—甲骨文...
- qt当前工程相对路径_QT绝对路径和相对路径小结
- macOS快速复制文件夹路径