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 参数,指定当前的开发模式,有 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 模块能够以各种形式表达他们的依赖关系。

  • es6import语句;
  • CommonJS: require() 语句;
  • AMDdefinerequire 语句;
  • 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
└─production
        index-0d30eb18d3678e852376.js
        index.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
│
└─production
        index.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中共享的模块,取出来生成单独的chunk
    splitChunks: {
        chunks: "all",  // async表示抽取异步模块,all表示对所有模块生效,initial表示对同步模块生效
        cacheGroups: {
            vendors: {  // 抽离第三方插件
                test: /[\\/]node_modules[\\/]/,     // 指定是node_modules下的第三方包
                name: "vendors",
                priority: -10                       // 抽取优先级
    		},
            utilCommon: {   // 抽离自定义的公共库
                name: "common",
                minSize: 0,     // 将引用模块分离成新代码文件的最小体积
                minChunks: 2,   // 表示将引用模块如不同文件引用了多少次,才能分离生成新chunk
                priority: -20
            }
    	}
    },
    // 为 webpack 运行时代码创建单独的chunk
    runtimeChunk:{
    	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,   // 表示将引用模块如不同文件引用了多少次,才能分离生成新chunk
    priority: -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
    └─production
            index-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/index
        chunkName=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.html
            filename: 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中共享的模块,取出来生成单独的chunk
        splitChunks: {
            chunks: "all",  // async表示抽取异步模块,all表示对所有模块生效,initial表示对同步模块生效
            cacheGroups: {
                vendors: {  // 抽离第三方插件
                    test: /[\\/]node_modules[\\/]/,     // 指定是node_modules下的第三方包
                    name: "vendors",
                    priority: -10                       // 抽取优先级
                },
                utilCommon: {   // 抽离自定义的公共库
                    name: "common",
                    minSize: 0,     // 将引用模块分离成新代码文件的最小体积
                    minChunks: 2,   // 表示将引用模块如不同文件引用了多少次,才能分离生成新chunk
                    priority: -20
                }
            }
        },
        // 为 webpack 运行时代码创建单独的chunk
        runtimeChunk:{
            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 的 CLIEngine
        formatter: 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"
  }
  • 12
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
可以使用 webpack打包多个 HTML 页面。以下是一种常见的配置方法: 1. 首先,安装必要的 webpack 插件: ``` npm install html-webpack-plugin --save-dev ``` 2. 在 webpack.config.js 文件中配置多个页面: ```javascript const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { entry: { page1: './src/page1.js', page2: './src/page2.js', }, output: { filename: '[name].bundle.js', path: path.resolve(__dirname, 'dist'), }, plugins: [ new HtmlWebpackPlugin({ filename: 'page1.html', template: './src/page1.html', chunks: ['page1'], }), new HtmlWebpackPlugin({ filename: 'page2.html', template: './src/page2.html', chunks: ['page2'], }), ], }; ``` 在上述示例中,我们配置了两个入口文件(page1.js 和 page2.js),分别对应两个 HTML 页面(page1.html 和 page2.html)。每个 HtmlWebpackPlugin 实例都指定了生的 HTML 文件名称、模板文件路径以及关联的入口文件。 3. 创建对应的 HTML 模板文件: 在 src 目录下创建 page1.html 和 page2.html 文件,可以在这里编写对应页面的 HTML 结构。 4. 运行打包命令: ``` npx webpack ``` 运行以上命令后,webpack 将会根据配置生多个 HTML 文件,并将对应的 JavaScript 文件注入到相应的 HTML 页面中。最终打包好的文件将存储在 dist 目录下。 这样,你就可以通过配置多个 HtmlWebpackPlugin 实例来打包多个 HTML 页面。每个 HTML 页面可以关联不同的入口文件,实现灵活的页面打包配置。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

paopao_wu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值