在找一份相对完整的Webpack项目配置指南么?这里有

时间:2022-12-18 15:08:47

Webpack已经出来很久了,相关的文章也有很多,然而比较完整的例子却不是很多,让很多新手不知如何下脚,下脚了又遍地坑

说实话,官方文档是蛮乱的,而且有些还是错的错的。。很多配置问题只有爬过坑才知道

本文首先介绍Webpack(3)的一些基础知识,然后以一个已经完成的小Demo,逐一介绍如何在项目中进行配置

该Demo主要包含编译Sass/ES6,提取(多个)CSS文件,提取公共文件,模块热更新替换,开发与线上环境区分,使用jQuery插件的方式、页面资源引入路径自动生成(可指定生成位置),热更新编译模版文件自动生成webpack服务器中的资源路径,编写一个简单的插件,异步加载模块 等基础功能

应该能帮助大家更好地在项目中使用Webpack3来管理前端资源

本文比较啰嗦,可以直接看第四部分Webpack3配置在Demo中的应用,或者直接去Fork这个Demo边看边玩

Webpack已升级为v4版本,优化之后性能提升好几倍,请移步这个 webpack4项目配置Demo,以及 这篇升级优化点

首先,学习Webpack,还是推荐去看官方文档,还是挺全面的,包括中文的和英文的,以及GitHub上关于webpack的项目issues,还有就是一些完整了例子,最后就是得自己练手配置,才能在过程中掌握好这枯燥的配置。

一 、为什么要用Webpack

首先,得知道为什么要用webpack

前端本可以直接HTML、CSS、Javascript就上了,不过如果要处理文件依赖、文件合并压缩、资源管理、使用新技术改善生活的时候,就得利用工具来辅助了。

以往有常见的模块化工具RequireJS,SeaJS等,构建工具Grunt、Gulp等,新的技术Sass、React、ES6、Vue等,要在项目中使用这些东西,不用工具的话就略麻烦了。

其实简单地说要聚焦两点:模块化以及自动构建。

模块化可以使用RequireJS来处理依赖,使用Gulp来进行构建;也可以使用ES6新特性来处理模块化依赖,使用webpack来构建

两种方式都狠不错,但潮流所驱,后者变得愈来愈强大,当然也不是说后者就替代了前者,只是大部分情况下,后者更好

二、什么是Webpack

如其名,Web+Pack 即web的打包,主要用于web项目中打包资源进行自动构建。

Webpack将所有资源视为JS的模块来进行构建,所以对于CSS,Image等非JS类型的文件,Webpack会使用相应的加载器来加载成其可识别的JS模块资源

通过配置一些信息,就能将资源进行打包构建,更好地实现前端的工程化

三、Webpack的基础配置

可以认为Webpack的配置是4+n模式,四个基本的 entry(入口设置)output(输出设置)loader(加载器设置)、plugin(插件设置),然后加上一些特殊功能的配置。

使用Webpack首先需要安装好NodeJS

node -v
npm -v

确保已经可以使用node,使用NPM包管理工具来安装相应依赖包(网络环境差可以使用淘宝镜像CNPM来安装)

npm install -g cnpm --registry=https://registry.npm.taobao.org
cnpm -v

全局安装好webpack包

npm i -g webpack
webpack -v

1. webpack的配置方式主要有三种

1. 通过cli命令行传入参数 

webpack ./src.js -o ./dest.js --watch --color 

2. 通过在一个配置文件设置相应配置,导出使用

// ./webpack.config.js文件
module.exports = {
   context: ...
entry: { },
output: { }
}; // 命令行调用(不指定文件时默认查找webpack.config.js)
webpack [--config webpack.config.js]

3. 通过使用NodeJS的API配置

这个和第二点有点类似,区别主要是第二种基本都是使用{key: value}的形式配置的,API则主要是一些调用

另外,某些插件的在这两种方式的配置上也有一些区别

最常用的是第二种,其次第三种,第一种不太建议单独使用(因为相对麻烦,功能相对简单)

2. 常见的几个配置属性

1. context  绝对路径

一般当做入口文件(包括但不限于JS、HTML模板等文件)的上下文位置,

默认使用当前目录,不过建议还是填上一个

// 上下文位置
context: path.resolve(__dirname, 'static')

2. entry  模块入口文件设置

可以接受字符串表示一个入口文件,不过一般来说是多页应用多,就设置成每页一个入口文件得了

比如home对应于一个./src/js/home模块,这里的key会被设置成webpack的一个chunk,即最终webpack会又三个chunkname:home | detail | common

也可以对应于多个模块,用数组形式指定,比如这里把jquery设置在common的chunk中

也可以设置成匿名函数,用于动态添加的模块

// 文件入口配置
entry: {
home: './src/js/home',
detail: './src/js/detail',
// 提取jquery入公共文件
common: ['jquery']
},

3. resolve 处理资源的查找引用方式

如上方其实是省略了后JS缀,又比如想在项目中引入util.js 可以省略后缀

import {showMsg} from './components/util';
// 处理相关文件的检索及引用方式
resolve: {
extensions: ['.js', '.jsx', '.json'],
modules: ['node_modules'],
alias: { }
},

4. output 设置文件的输出

最基础的就是这三个了

path指定输出目录,要注意的是这个目录影响范围是比较大,与该chunk相关的资源生成路径是会基于这个路径的

filename指定生成的文件名,可以使用[name] [id]来指定相应chunk的名称,如上的home和detail,用[hash]来指定本次webpack编译的标记来防缓存,不过建议是使用[chunkhash]来依据每个chunk单独来设置,这样不改变的chunk就不会变了

hash放在?号之后的好处是,不会生成新的文件(只是文件内容被更改了),同时hash会附在引用该资源的URL后(如script标签中的引用)

publicPath指定所引用资源的目录,如在html中的引用方式,建议设置一个

// 文件输出配置
output: {
// 输出所在目录
path: path.resolve(__dirname, 'static/dist/js'),
filename: '[name].js?[chunkhash:8]'// 设置文件引用主路径
publicPath: '/public/static/dist/js/'
}

5.devtool指定sourceMap的配置

如果开启了,就可以在浏览器开发者工具查看源文件

// 启用sourceMap
devtool: 'cheap-module-source-map',

在找一份相对完整的Webpack项目配置指南么?这里有

比如这里就是对应的一个source Map,建议在开发环境下开启,帮助调试每个模块的代码

这个配置的选项是满多的,而且还可以各种组合,按照自己的选择来吧

在找一份相对完整的Webpack项目配置指南么?这里有

6. module指定模块如何被加载

通过设置一些规则,使用相应的loader来加载

主要就是配置module的rules规则组,通过use字段指定loader,如果只有一个loader,可以直接用字符串,loader要设置options的就换成数组的方式吧

或者使用多个loader的时候,也用数组的形式,规则不要用{ }留空,在windows下虽然正常,但在Mac下会报错提示找不到loader

多个loader遵循从右到左的pipe 的方式,如下 eslint-loader是先于babel-loader执行的

通过exclude或include等属性再确定规则的匹配位置

// 模块的处理配置,匹配规则对应文件,使用相应loader配置成可识别的模块
module: {
rules: [{
test: /\.css$/,
use: 'css-loader'
}, {
test: /\.jsx?$/,
// 编译js或jsx文件,使用babel-loader转换es6为es5
exclude: /node_modules/,
use: [{
loader: 'babel-loader',
options: { }
}, {
loader: 'eslint-loader'
}]
}

7.  plugins设置webpack配置过程中所用到的插件

比如下方为使用webpack自带的提取公共JS模块的插件

// 插件配置
plugins: [
// 提取公共模块文件
new webpack.optimize.CommonsChunkPlugin({
chunks: ['home', 'detail'],
filename: '[name].js',
name: 'common'
}),
new ... ]

这就是webpack最基础的东西了,看起来内容很少,当然还有其他很多,但复杂的地方在于如何真正去使用这些配置

四、Webpack配置在Demo中的应用

下面以一个相对完整的基础Demo着手,介绍一下几个基本功能该如何配置

Demo项目地址   建议拿来练练

在找一份相对完整的Webpack项目配置指南么?这里有

在找一份相对完整的Webpack项目配置指南么?这里有

在找一份相对完整的Webpack项目配置指南么?这里有

1. 搭建个服务器

既然是Demo,至少就得有一个服务器,用node来搭建一个简单的服务器,处理各种资源的请求返回

在找一份相对完整的Webpack项目配置指南么?这里有

新建一个服务器文件server.js,以及页面文件目录views,其他资源文件目录public

服务器文件很简单,请求什么就返回什么,外加了一个gzip的功能

let http = require('http'),
fs = require('fs'),
path = require('path'),
url = require('url'),
zlib = require('zlib'); http.createServer((req, res) => {
let {pathname} = url.parse(req.url),
acceptEncoding = req.headers['accept-encoding'] || '',
referer = req.headers['Referer'] || '',
raw; console.log('Request: ', req.url); try {
raw = fs.createReadStream(path.resolve(__dirname, pathname.replace(/^\//, ''))); raw.on('error', (err) => {
console.log(err); if (err.code === 'ENOENT') {
res.writeHeader(404, {'content-type': 'text/html;charset="utf-8"'});
res.write('<h1>404错误</h1><p>你要找的页面不存在</p>');
res.end();
}
}); if (acceptEncoding.match(/\bgzip\b/)) {
res.writeHead(200, { 'Content-Encoding': 'gzip' });
raw.pipe(zlib.createGzip()).pipe(res);
} else if (acceptEncoding.match(/\bdeflate\b/)) {
res.writeHead(200, { 'Content-Encoding': 'deflate' });
raw.pipe(zlib.createDeflate()).pipe(res);
} else {
res.writeHead(200, {});
raw.pipe(res);
}
} catch (e) {
console.log(e);
}
}).listen(8088); console.log('服务器开启成功', 'localhost:8088/');

2. 设置基础项目目录

页面文件假设采用每一类一个目录,目录下的tpl为源文件,另外一个为生成的目标页面文件

在找一份相对完整的Webpack项目配置指南么?这里有

/public目录下,基本配置文件就放在根目录下,JS,CSS,Image等资源文件就放在/public/static目录下

我们要利用package.json文件来管理编译构建的包依赖,以及设置快捷的脚本启动方式,所以,先在/public目录下执行 npm init 吧

public/static/dist目录用来放置编译后的文件目录,最终页面引用的将是这里的资源

public/static/imgs目录用来放置图片源文件,有些图片会生成到dist中

public/static/libs目录主要用来放置第三方文件,也包括那些很少改动的文件

public/static/src 用来放置js和css的源文件,相应根目录下暴露一个文件出来,公共文件放到相应子目录下(如js/components和scss/util)

在找一份相对完整的Webpack项目配置指南么?这里有 在找一份相对完整的Webpack项目配置指南么?这里有

最后文件结构看起来是这样的,那就可以开干了

3. 开发和生产环境的Webpack配置文件区分

首先在项目目录下安装webpack吧

npm i webpack --save-dev

用Webpack来构建,在开发环境和生产环境的配置还是有一些区别的,构建是耗时的,比如在开发环境下就不需要压缩文件、计算文件hash、提取css文件、清理文件目录这些辅助功能了,而可以引入热更新替换来加快开发时的模块更新效率。

所以建议区分一下两个环境,同时将两者的共同部分提取出来便于维护

在找一份相对完整的Webpack项目配置指南么?这里有

NODE_ENV是nodejs在执行时的环境变量,webpack在运行构建期间也可以访问这个变量,所以我们可以在dev和prod下配置相应的环境变量

这个配置写在package.json里的scripts字段就好了,比如

"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build:dev": "export NODE_ENV=development && webpack-dev-server --config webpack.config.dev.js",
"build:prod": "export NODE_ENV=production && webpack --config webpack.config.prod.js --watch "
},

这样一来,我们就可以直接用 npm run build:prod来执行生产环境的配置命令(设置了production的环境变量,使用prod.js)

直接用npm run build:dev来执行开发环境的配置命令(设置了development的环境变量,使用dev.js,这里还使用了devServer,后面说)

注意这里是Unix系统配置环境变量的写法,在windows下,记得改成 SET NODE_ENV=development&& webpack-dev-server.......(&&前不要空格)

然后就可以在common.js配置文件中获取环境变量

// 是否生产环境
isProduction = process.env.NODE_ENV === 'production',

然后可以在plugins中定义一个变量提供个编译中的模块文件使用

// 插件配置
plugins: [
// 定义变量,此处定义NODE_ENV环境变量,提供给生成的模块内部使用
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify(process.env.NODE_ENV)
}
}),

这样一来,我们可以在home.js中判断是否为开发环境来引入一些文件

// 开发环境时,引入页面文件,方便改变页面文件后及时模块热更新
if (process.env.NODE_ENV === 'development') {
require('../../../../views/home/home.html');
}

然后我们使用webpack-merge工具来合并公共配置文件和开发|生产配置文件

npm i webpack-merge --save-dev

merge = require('webpack-merge')

commonConfig = require('./webpack.config.common.js')

/**
* 生产环境Webpack打包配置,整合公共部分
* @type {[type]}
*/
module.exports = merge(commonConfig, {
// 生产环境不开启sourceMap
devtool: false, // 文件输出配置
output: {
// 设置文件引用主路径
publicPath: '/public/static/dist/js/'
}, // 模块的处理配置

4. 设置公共模块

公共模块其实可以分为JS和CSS两部分(如果有提取CSS文件的话)

在公共文件的plugin中加入

// 提取公共模块文件
new webpack.optimize.CommonsChunkPlugin({
chunks: ['home', 'detail'],
// 开发环境下需要使用热更新替换,而此时common用chunkhash会出错,可以直接不用hash
filename: '[name].js' + (isProduction ? '?[chunkhash:8]' : ''),
name: 'common'
}),

设置公共文件的提取源模块chunks,以及最终的公共文件模块名

公共模块的文件的提取规则是chunks中的模块公共部分,如果没有公共的就不会提取,所以最好是在entry中就指定common模块初始包含的第三方模块,如jquery,react等

 // 文件入口配置
entry: {
home: './src/js/home',
detail: './src/js/detail',
// 提取jquery入公共文件
common: ['jquery']
},

5. 编译ES6成ES5

要讲ES6转换为ES5,当然首用babel了,先安装loader及相关的包

npm i babel-core babel-loader babel-preset-env babel-polyfill babel-plugin-transform-runtime --save-dev

-env包主要用来配置语法支持度

-polyfill用来支持一些ES6拓展的但babel转换不了的方法(Array.from Generator等)

-runtime用来防止重复的ES6编译文件所需生成(可以减小文件大小)

然后在/public根目录下新建 .babelrc文件,写入配置

{
"presets": [
"env"
],
"plugins": ["transform-runtime"]
}

然后在common.js的配置文件中新增一条loader配置就行了,注意使用exclude排除掉不需要转换的目录,否则可能会出错哦

{
test: /\.jsx?$/,
// 编译js或jsx文件,使用babel-loader转换es6为es5
exclude: /node_modules/,
use: [{
loader: 'babel-loader',
options: { }
}]
}

6. 编译Sass成CSS,嵌入到页面<style>标签中,或将其提取出(多个)CSS文件来用<link>引入

sass的编译node-sass需要python2.7的环境,先确定已经安装并设置了环境变量

npm i sass-loader node-sass style-loader css-loader --save-dev

类似的,设置一下loader规则

不过这里要设置成使用提取CSS文件的插件设置了,因为它的disable属性可以快速切换是否提取CSS(这里设置成生产环境才提取)

好好看这个栗子,其实分三步:设置(new)两个实例,loader匹配css和sass两种文件规则,在插件中引入这两个实例

提取多个CSS文件其实是比较麻烦的,但也不是不可以,方法就是设置多个实例和对应的几个loader规则

这里把引入的sass当做是自己写的文件,提取成一个文件[name].css,把引入的css当做是第三方的文件,提取成一个[name]_vendor.css,既做到了合并,也做到了拆分,目前还没想到更好的方案

上面提到过,output的path设置成了/public/static/dist/js ,所以这里的filename 生成是基于上面的路径,可以用../来更换生成的css目录

[contenthash]是css文件内容的hash,在引用它的地方有体现

fallback表示不可提取时的代替方案,即上述所说的使用style-loader嵌入到<style>标签中

npm i extract-text-webpack-plugin --save-dev

ExtractTextWebpackPlugin = require('extract-text-webpack-plugin')

/ 对import 引入css(如第三方css)的提取
cssExtractor = new ExtractTextWebpackPlugin({
// 开发环境下不需要提取,禁用
disable: !isProduction,
filename: '../css/[name]_vendor.css?[contenthash:8]',
allChunks: true
}) // 对import 引入sass(如自己写的sass)的提取
sassExtractor = new ExtractTextWebpackPlugin({
// 开发环境下不需要提取,禁用
disable: !isProduction,
filename: '../css/[name].css?[contenthash:8]',
allChunks: true
}); // 插件配置
plugins: [
// 从模块中提取CSS文件的配置
cssExtractor,
sassExtractor
] module: {
rules: [{
test: /\.css$/,
// 提取CSS文件
use: cssExtractor.extract({
// 如果配置成不提取,则此类文件使用style-loader插入到<head>标签中
fallback: 'style-loader',
use: [{
loader: 'css-loader',
options: {
// url: false,
minimize: true
}
},
// 'postcss-loader'
]
})
}, {
test: /\.scss$/,
// 编译Sass文件 提取CSS文件
use: sassExtractor.extract({
// 如果配置成不提取,则此类文件使用style-loader插入到<head>标签中
fallback: 'style-loader',
use: [
'css-loader',
// 'postcss-loader',
{
loader: 'sass-loader',
options: {
sourceMap: true,
outputStyle: 'compressed'
}
}
]
})
}

这样一来,如果在不同文件中引入不同的文件,生成的css可能长这样

// ./home.js
import '../../libs/bootstrap-datepicker/datepicker3.css'; import '../../libs/chosen/chosen.1.0.0.css'; import '../../libs/layer/skin/layer.css'; import '../../libs/font-awesome/css/font-awesome.min.css'; import '../scss/detail.scss'; // ./detail.js
import '../../libs/bootstrap-datepicker/datepicker3.css'; import '../../libs/chosen/chosen.1.0.0.css'; import '../../libs/layer/skin/layer.css'; import '../scss/detail.scss';

在找一份相对完整的Webpack项目配置指南么?这里有

// ./home.html
<link href="/public/static/dist/js/../css/common_vendor.css?66cb1f48" rel="stylesheet">
<link href="/public/static/dist/js/../css/common.css?618d2a04" rel="stylesheet">
<link href="/public/static/dist/js/../css/home_vendor.css?12a314c8" rel="stylesheet">
<link href="/public/static/dist/js/../css/home.css?c196fc33" rel="stylesheet"> // ./detail.html
<link href="/public/static/dist/js/../css/common_vendor.css?66cb1f48" rel="stylesheet">
<link href="/public/static/dist/js/../css/common.css?618d2a04" rel="stylesheet">

可以看到,公共文件也被提取出来了,利用HtmlWebpackPlugin就能将其置入了

另外,可以看到这里的绝对路径,其实就是因为在output中设置了publicPath为/public/static/dist/js/

当然了,也不是说一定得在js中引入这些css资源文件,你可以直接在页面中手动<link>引入第三方CSS

我这里主要是基于模块化文件依赖,以及多CSS文件的合并压缩的考虑才用这种引入方式的

7. jQuery插件的引入方式

目前来说,jQuery及其插件在项目中还是很常用到的,那么就要考虑如何在Webpack中使用它

第一种方法,就是直接页面中<script>标签引入了,但这种方式不受模块化的管理,好像有些不妥

第二种方法,就是直接在模块中引入所需要的jQuery插件,而jQuery本身由Webpack插件提供,通过ProvidePlugin提供模块可使用的变量$|jQuery|window.jQuery

不过这种方法好像也有不妥,把所有第三方JS都引入了,可能会降低编译效率,生成的文件也可能比较臃肿

npm i jquery --save

//
plugins: [
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
'window.jQuery': 'jquery'
}), ] // ./home.js import '../../libs/bootstrap-datepicker/bootstrap-datepicker.js';
console.log('.header__img length', jQuery('.header__img').length);

第三种办法,可以在模块内部直接引入jQuery插件,也可以直接在页面通过<script>标签引入jQuery插件,而jQuery本身由Webpack的loader导出为全局可用

上述ProvidePlugin定义的变量只能在模块内部使用,我们可以使用expose-loader将jQuery设置为全局可见

npm i expose-loader --save

// 添加一条规则
{
test: require.resolve('jquery'),
// 将jQuery插件变量导出至全局,提供外部引用jQuery插件使用
use: [{
loader: 'expose-loader',
options: '$'
}, {
loader: 'expose-loader',
options: 'jQuery'
}]
}

要注意在Webpack3中不能使用webpack.NamedModulesPlugin()来获取模块名字,它会导致expose 出错失效(bug)

在找一份相对完整的Webpack项目配置指南么?这里有

不过现在问题又来了,这个应该是属于HtmlWebpackPlugin的不够机智的问题,先说说它怎么用吧

8. HtmlWebpackPlugin将页面模板编译成最终的页面文件,包含JS及CSS资源的引用

第一个重要的功能就是生成对资源的引入了,第二个就是帮助我们填入资源的chunkhash值防止浏览器缓存

这个在生产环境使用就行了,开发环境是不需要的

npm i html-webpack-plugin --save-dev

HtmlWebpackPlugin = require('html-webpack-plugin')

plugins: [

 // 设置编译文件页面文件资源模块的引入
new HtmlWebpackPlugin({
// 模版源文件
template: '../../views/home/home_tpl.html',
// 编译后的目标文件
filename: '../../../../views/home/home.html',
// 要处理的模块文件
chunks: ['common', 'home'],
// 插入到<body>标签底部
inject: true
}),
new HtmlWebpackPlugin({
template: '../../views/detail/detail_tpl.html',
filename: '../../../../views/detail/detail.html',
chunks: ['common', 'detail'],
inject: true
}), ]

使用方式是配置成插件的形式,想对多少个模板进行操作就设置多少个实例

注意template是基于context配置中的上下文的,filename是基于output中的path路径的

// ./home_tpl.html

    <script src="/public/static/libs/magicsearch/jquery.magicsearch2.js"></script>
</body> // ./home.html <script src=/public/static/libs/magicsearch/jquery.magicsearch2.js></script>
<script type="text/javascript" src="/public/static/dist/js/common.js?cc867232"></script>
<script type="text/javascript" src="/public/static/dist/js/home.js?5d4a7836"></script>
</body>

它会编译成这样,然而,然而,要注意到这里是有问题的

这里有个jQuery插件,而Webpack使用expose是将jQuery导出到了全局中,我们通过entry设置把jQuery提取到了公共文件common中

所以正确的做法是common.js文件先于jQuery插件加载

而这个插件只能做到在<head> 或<body>标签尾部插入,我们只好手动挪动一下<script>的位置

不过,我们还可以基于这个插件,再写一个插件来实现自动提升公共文件 <script>标签到最开始

HtmlWebpackPlugin运行时有一些事件

    html-webpack-plugin-before-html-generation
html-webpack-plugin-before-html-processing
html-webpack-plugin-alter-asset-tags
html-webpack-plugin-after-html-processing
html-webpack-plugin-after-emit
html-webpack-plugin-alter-chunks

在编译完成时,正则匹配到<script>标签,找到所设置的公共模块(可能设置了多个公共模块),按实际顺序提升这些公共模块即可

完整代码如下:

 // ./webpack.myPlugin.js

 let extend = require('util')._extend;

 // HtmlWebpackPlugin 运行后调整公共script文件在html中的位置,主要用于jQuery插件的引入
function HtmlOrderCommonScriptPlugin(options) {
this.options = extend({
commonName: 'common'
}, options);
} HtmlOrderCommonScriptPlugin.prototype.apply = function(compiler) {
compiler.plugin('compilation', compilation => {
compilation.plugin('html-webpack-plugin-after-html-processing', (htmlPluginData, callback) => {
// console.log(htmlPluginData.assets); // 组装数组,反转保证顺序
this.options.commonName = [].concat(this.options.commonName).reverse(); let str = htmlPluginData.html,
scripts = [],
commonScript,
commonIndex,
commonJS; //获取编译后html的脚本标签,同时在原html中清除
str = str.replace(/(<script[^>]*>(\s|\S)*?<\/script>)/gi, ($, $1) => {
scripts.push($1);
return '';
}); this.options.commonName.forEach(common => {
if (htmlPluginData.assets.chunks[common]) {
// 找到公共JS标签位置
commonIndex = scripts.findIndex(item => {
return item.includes(htmlPluginData.assets.chunks[common].entry);
}); // 提升该公共JS标签至顶部
if (commonIndex !== -1) {
commonScript = scripts[commonIndex];
scripts.splice(commonIndex, 1);
scripts.unshift(commonScript);
}
}
}); // 重新插入html中
htmlPluginData.html = str.replace('</body>', scripts.join('\r\n') + '\r\n</body>'); callback(null, htmlPluginData);
});
});
}; module.exports = {
HtmlOrderCommonScriptPlugin,
};

然后,就可以在配置中通过插件引入了

{HtmlOrderCommonScriptPlugin} = require('./webpack.myPlugin.js');

// HtmlWebpackPlugin 运行后调整公共script文件在html中的位置,主要用于jQuery插件的引入
new HtmlOrderCommonScriptPlugin({
// commonName: 'vendor'
})

亲测还是蛮好用的,可以应对简单的需求了

9. 使用url-loader和file-loader和html-loader来处理图片、字体等文件的资源引入路径问题

这个配置开发环境和生产环境是不同的,先看看生产环境的,主要的特点是有目录结构的设置,设置了一些生成的路径以及名字信息

开发环境因为是使用了devServer,不需要控制目录结构

npm i url-loader file-loader@0.10.0 html-loader --save-dev

这里要注意的是file-loader就不要用0.10版本以上的了,会出现奇怪的bug,主要是下面设置的outputPath和publicPath和[path]会不按套路出牌

导致生成的页面引用资源变成奇怪的相对路径

rules: [{
test: /\.(png|gif|jpg)$/,
use: {
loader: 'url-loader',
// 处理图片,当大小在范围之内时,图片转换成Base64编码,否则将使用file-loader引入
options: {
limit: 8192,
// 设置生成图片的路径名字信息 [path]相对context,outputPath输出的路径,publicPath相应引用的路径
name: '[path][name].[ext]?[hash:8]',
outputPath: '../',
publicPath: '/public/static/dist/',
}
}
}, {
test: /\.(eot|svg|ttf|otf|woff|woff2)\w*/,
use: [{
loader: 'file-loader',
options: {
// 设置生成字体文件的路径名字信息 [path]相对context,outputPath输出的路径,publicPath相应引用的主路径
name: '[path][name].[ext]?[hash:8]',
outputPath: '../',
publicPath: '/public/static/dist/',
// 使用文件的相对路径,这里先不用这种方式
// useRelativePath: isProduction
}
}],
}, {
test: /\.html$/,
// 处理html源文件,包括html中图片路径加载、监听html文件改变重新编译等
use: [{
loader: 'html-loader',
options: {
minimize: true,
removeComments: false,
collapseWhitespace: false
}
}]
}]

比较生涩难懂,看个栗子吧

scrat.png是大于8192的,最终页面引入会被替换成绝对路径,并且带有hash防止缓存,而输出的图片所在位置也是用着相应的目录,便于管理

// ./home_tpl.html

        <img class="header__img" src="../../public/static/imgs/kl/scrat.png" width="200" height="200">

// ./home.html

        <img class=header__img src=/public/static/dist/imgs/kl/scrat.png?8ad54ef5 width=200 height=200>

在找一份相对完整的Webpack项目配置指南么?这里有

如果换个小图,就会替换成base64编码了,在css中的引入也一样

<img class=header__img src=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAARgAAAA6CAYAAABrnUYFAAAaVElEQVR4Xu1df3wdVZX/npnkNUlbWn6sFhAELSgibSYtCIhagXXB0vdSsOwu4CKiXbAglk8zAVwloELzwoqIUsFFRXQVaiEvRcAfLD9EfqxtXtqKS239AQoFRUlb+tK+5N2znzNvJp1M34+ZeZOElLn/QPPuPffcc2e+c+75dQlxiyUQSyCWwChJgEaJbkw2lkAsgVg

再来看看开发环境的

rules: [{
test: /\.(png|gif|jpg)$/,
// 处理图片,当大小在范围之内时,图片转换成Base64编码,否则将使用file-loader引入
use: [{
loader: 'url-loader',
options: {
limit: 8192
}
}]
}, {
test: /\.(eot|svg|ttf|otf|woff|woff2)\w*/,
// 引入文件
use: 'file-loader'
}]

10. 模块热更新替换的正确姿势

在开发环境下,如果做到模块的热更新替换,效果肯定是棒棒的。生成环境就先不用了

在最初的时候,只是做到了热更新,并没有做到热替换,其实都是坑在作祟

热更新,需要一个配置服务器,Webpack集成了devServer的nodejs服务器,配置一下它

// 开发环境设置本地服务器,实现热更新
devServer: {
contentBase: path.resolve(__dirname, 'static'),
// 提供给外部访问
host: '0.0.0.0',
port: 8188,
// 设置页面引入
inline: true
},

正常的话,启动服务应该就可以了吧

webpack-dev-server --config webpack.config.dev.js

要记住,devServer编译的模块是输出在服务器上的(默认根目录),不会影响到本地文件,所以要在页面上手动设置一下引用的资源

<script src="http://localhost:8188/common.js"></script>
<script src="http://localhost:8188/home.js"></script>

浏览器访问,改动一下home.js文件,这时应该可以看到页面自动刷新,这就是热更新了