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 参数,指定当前的开发模式,有 developmentproduction 两个值:

module.exports = {mode: 'production'
};

也可以通过 CLI 参数传递:

webpack --mode=production
development 会将 process.env.NODE_ENV 的值设为development。启用 NamedChunksPluginNamedModulesPlugin
production 会将 process.env.NODE_ENV 的值设为 production。启用 FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin,OccurrenceOrderPlugin, SideEffectsFlagPluginUglifyJsPlugin

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: definerequire 语句;
  • 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']}]
}
  1. 首先是 sass-loader 加载 .scss 文件做转换工作,因为.scss 文件中的内容无法直接被浏览器直接识别。
  2. scss 转换成 css代码后,再由 css-loader 来加载css代码
  3. 最终加载的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多页面相关推荐

  1. Android Studio 打包H5网址页面,封装APK

    Android Studio 打包H5网址页面,封装APK 一.下载 AndroidStudio 二.配置SDK 三.新建项目 四.配置项目 MainActivity WebViewClient An ...

  2. webpack4打包实战

    一.模块化打包工具的由来 ES Module存在环境兼容问题,通过模块化方式划分的模块较多,网络请求频繁,在前端应用开发中不仅仅需要JavaScript代码需要模块化,随着应用的日益复杂,html,c ...

  3. 详细解剖大型H5单页面应用的核心技术点

    阐述下项目 Xut.js 开发中一个比较核心的优化技术点,这是一套平台代码,并非某一个插件功能或者框架可以直接拿来使用,核心代码大概是6万行左右(不包含任何插件) .这也并非一个开源项目,不能商业使用 ...

  4. LAYA和TypeScript制作H5入门——页面制作

    上一篇: LAYA和TypeScript制作H5入门--准备工作 一般情况下,一个H5是由多个相互关联的ui页面串联组成.遵循的大致逻辑一般是加载页面,开始页面,内容页面和结束页面.这些界面承担了用户 ...

  5. uniapp开发,打包成H5部署到服务器

    哈喽大家好~我是马小跳.一名进阶中的程序媛. 在这里记录下自己成长的每一次进步,希望遇到志同道合的猿友 一起努力,一起把技术up up up!!! 前端使用uniapp开发项目完成后,需要将页面打包, ...

  6. uniapp的打包:h5、微信小程序以及APP方式

    uniapp的打包:h5.微信小程序以及APP方式 H5打包 微信小程序打包 App打包 本人用的是HBuilder编译器,学习uniapp时b站某位大大推荐的,我刚开始接触代码时候也用过,那时候并不 ...

  7. uniapp-怎么把项目打包成H5

    如果没有项目的话就自己创建项目uni-app 创建完项目我们会得到一个uniapp框架,可以往里面添加属于自己的功能和需求 但我们把需求和功能都写好,就是发布的时候,通过uniapp 我们可以发布成  ...

  8. 将uniapp打包成h5放在安卓webview中(解决uniapp引入第三方地图卡顿问题)

    本来是使用uniapp进行开发,然后打包成安卓软件的,因为是用了地图模块(基于天地图),因为uniapp框架的限制,只能使用webview组件引入地图文件,然后出现一个问题,发现地图在浏览器中打开很流 ...

  9. mustache.js html模板,js模板引擎Mustache将h5模板页面转化为小程序页面

    研究目的 怎么让小程序页面可以通过后台h5模板进行管理 Mustache 简单语法示例 1.{{keyName}} var data = { "name": "zhang ...

最新文章

  1. 技术人的不惑之路...... | 每日趣闻
  2. laravel中的where和orwhere的源码分析
  3. 常用网络故障集锦,收藏备用
  4. python socket编程实现的简单tcp迭代server
  5. VSTO学习笔记(二)Excel对象模型
  6. 2019/12/11学习内容摘要(Linux系统用户与用户组管理①)
  7. vagrant使用_使用Vagrant的初学者指南
  8. Python_base_正则表达式
  9. 用AtomicStampedReference解决ABA问题
  10. Studio 3T for MongoDB 破解教程
  11. nuke linux 插件,NUKE插件-Pos工具包V1.2
  12. springboot的web进阶知识(2)
  13. iptables实现网卡包的转发
  14. 自然辩证法论文 计算机,自然辩证法对人工智能学的指导意义
  15. PHP 与 JSP 比较(PHP、ASP、JSP是什么)
  16. msxml3.dll 错误 '800c0005'具体解决办法详解
  17. 电脑上怎么绘制流程图以及在线绘制方法
  18. 为什么跑椭圆机比跑步更累
  19. recover database
  20. 前端开发:Vue中v-if和v-show的使用以及应用场景

热门文章

  1. 【DBA充电宝】和【Python充电宝】DBA充电宝和Python充电宝微信群已建立,可加我微信(lhrbestxh)入群...
  2. 【重识前端】深入内存世界
  3. Hi3518E开发板上添加RT5370模块
  4. mysql 姓刘或姓李_案例-多表查询、子查询实例02(有答案)
  5. 【Golang | gRPC】gRPC-Client Streaming客户端流实战
  6. sandboxie游戏不能运行在虚拟环境中如何解决_软件测试中的虚拟化
  7. 云服务器系统能改win7的吗,云服务器系统能改win7的吗
  8. c语言汉字属于什么类型_设计源于传承|为什么字体设计太难?独属于我们的汉字—甲骨文...
  9. qt当前工程相对路径_QT绝对路径和相对路径小结
  10. macOS快速复制文件夹路径