webpack折腾记(二)
最近很忙很忙,没法描述的那种忙:一个人搭后台,接webview,写活动页面...要不是之前简单折腾了下webpack,估计现在更是忙的不可开交。在此期间遇见了许多对于开发环境的需求(貌似webpack3也出来了),因此可以继续折腾折腾。
这里先列举我遇见的需求及对应的解决方案,然后参考vue-cli
的配置从头搭建一个较完善的开发环境模板。
常见需求
下面的描述是一个关于APP分享的项目需求:
- APP分享由web页面实现:APP内部由一个关于分享的介绍页面,然后分享链接打开有一个下载页面,除此之外还有一个具体业务的分享页面
- 具体业务分享页面和前面两个页面的UI差别较大,样式无法复用且有独立的业务逻辑
- 打包的页面需要接到后台模板(现在的后台是
Laravel
,因此需要打包成xxx.blade.php
)
虽然需求不复杂,但是为这个需求搭建一个合适的开发环境却遇见了下面的这些问题(自己给自己找事~),下面来各个击破。
打包多个文件
将html
页面打包成blade
模板,可以使用,可以使用html-webpack-plugin
插件来实现。将多个入口文件打包成多个出口文件,然后在对应的输出模板上加载对应的出口文件,这个需求相对要复杂一点,我们先将需求拆分成:
- 将多个入口文件打包成多个输入文件,比如将
page1.js
和page2.js
输出为bundle1.js
和bundle2.js
- 输出多个模板文件,比如讲
page1.html
和page2.html
输出为page1.blade.php
和page2.blade.php
- 控制每个模板文件中加载的模块
- 实现我们最终多对多的需求
步骤一
针对第一点,我们可以使用entry
配置项的对象形式,,实现起来就很简单了
entry: {
page1: `${DEMO_DIR}/page1.js`,
page2: `${DEMO_DIR}/page2.js`
},
output: {
path: OUTPUT_DIR,
filename: "[name].js"
},
注意output.filename
的[name]
,这是输出文件名一个特殊的用法,对应的是入口文件名,这里就是我们配置的page1
和page2
。除此之外还有[id]
,[hash]
等用法,这里就不展开了,感兴趣的同学可移步这里。
步骤二
输出多个模板文件就更简单了,html-webpack-plugin
插件本身就支持输出多个文件,通过构造参数的template
和filename
属性指定模板文件和输出文件即可。
plugins: [
new HtmlWebpackPlugin({
template: `${DEMO_DIR}/tpl/page1.html`,
filename: "page1.html",
}),
new HtmlWebpackPlugin({
template: `${DEMO_DIR}/tpl/page2.html`,
filename: "page2.html",
}),
]
接着步骤一的配置进行打包,可以发现在OUTPUT_DIR
下生成了page1.html
和page2.html
,貌似我们的目的已经达到了。不行的是打开文件可以发现,每个模板都将page1.js
和page2.js
引入了,这就出事情了,打包的目的不就是减少加载文件的数量吗?引入多个文件不如将他们打包成一个文件呢~
步骤三
要解决这个问题也很容易,HtmlWebpackPlugin
还支持chunks
的配置,该属性接受一个数组表示需要引入的输出模块,在步骤二的基础上稍作修改
new HtmlWebpackPlugin({
template: `${DEMO_DIR}/tpl/page1.html`,
filename: "page1.html",
chunks: ["page1"],
}),
new HtmlWebpackPlugin({
template: `${DEMO_DIR}/tpl/page2.html`,
filename: "page2.html",
chunks: ["page2"]
}),
现在继续打包,可以发现每个模板都只加载了对应的模块,所以官方文档才是最好的教程啊。
需要注意的是,这里的chunks
对应的模块名,指的是entry
配置中模块的属性名(这里只是恰好属性名与文件名相同了),希望不要造成歧义~
步骤四
咦?我们的需求貌似已经实现了~等等,是不是忘记了什么...对了,样式表!在各自的入口文件引入对应的scss文件,然后使用extract-text-webpack-plugin
插件将样式表进行打包的文件中进行分离,问题是:如何分离多个样式表文件呢?
跟使用多次HtmlWebpackPlugin
插件类似,我们可以定义多个ExtractTextPlugin
对象,然后在rules中针对样式表文件定义更加详细的规则。由于使用ExtractTextPlugin
本身的过程就要设置几个步骤,这里要稍微绕一点点 。
// 首先定义多个实例
let page1ExtractTextPlugin = new ExtractTextPlugin({
filename: "page1.css"
});
let page2ExtractTextPlugin = new ExtractTextPlugin({
filename: "page2.css"
});
// 针对具体的样式表指定其分离规则
module: {
rules: [
{
test: /page1.scss$/,
use: page1ExtractTextPlugin.extract({
use: ["css-loader", "autoprefixer-loader", "sass-loader"],
})
},
{
test: /page2.scss$/,
use: page2ExtractTextPlugin.extract({
use: ["css-loader", "autoprefixer-loader", "sass-loader"],
})
}
]
},
// 记得在plugins中调用,不然会报错哦
plugins: [
page1ExtractTextPlugin,
page2ExtractTextPlugin,
]
打包可以看见,我们的目的已经达到了,由于是在不同的入口文件中加载了不同的样式表,这些样式表都会自动注入到对应的模板上,而不需要我们的chunks
属性中去指定。这里也可以理解为webpack的核心是围绕着入口文件来工作的。
尽管打包多个文件的需求貌似已经完成了,但是可以看见,上面的配置明显比较冗余,接下来搭建的webpack模板就需要解决这个问题,在此之前,再看一看其他的常见需求。
内联文件
把CSS代码放在JS文件里面很明显不是一个明智的做法,但是单独提一个样式表出来有时候也没有必要(比如一个独立的活动介绍页面,生命周期只有两三天),此外为了加载效率,将样式表内联貌似还不错。 那么,如何实现样式表的内联呢?
这里需要用到基于HtmlWebpackPlugin
的插件html-webpack-inline-source-plugin
。 该插件可以为HtmlWebpackPlugin
拓展一个inlineSource
的配置,该参数接收一个正则表达式形式的字符串,用于将匹配的文件类型内联到模板上。
let HtmlWebpackInlineSourcePlugin = require('html-webpack-inline-source-plugin');
// 在插件中引入 HtmlWebpackInlineSourcePlugin,此时 HtmlWebpackPlugin 就会多一个 inlineSource 的配置项
plugins: [
new HtmlWebpackInlineSourcePlugin(),
new HtmlWebpackPlugin({
template: `${DEMO_DIR}/tpl/page1.html`,
filename: "page1.html",
chunks: ["page1"],
inlineSource: ".(css)$"
}),
]
将inlineSource
的参数修改为 ".(js|css)$"
,就可以将JS和CSS文件文件都内联到模板上面(但是由于JS文件需要babel转义,因此内联到页面上并不是很合适),内联文件更适合那种只有样式的静态页面,使用内联还可以减少将文件上传到CDN上的工作量呢。
模板变量
貌似上面的工作都是围绕着页面模板来转的。这是理所应当的,页面是前端的基础嘛。实际上HtmlWebpackPlugin
插件对于模板的支持远远超过了我们的想象,比如,我们可以使用直接使用ejs
模板来代替纯HTML
模板,这意味着,我们可以使用函数,变量等一系列语法特性来更快速地完成我们的工作。
先来验证一下这是不是真的
// page1.ejs
<h1>page1</h1>
<% var names = ["aaa", "bbb", "ccc"]; %>
<% if (names.length) { %>
<ul>
<% names.forEach(function(name){ %>
<li class="<%= name %>"><%= name %></li>
<% }) %>
</ul>
<% } %>
// 记得修改 HtmlWebpackPlugin 的 template 配置参数
打包,走你~发现模板真的生成了对应得三个li标签,这对于编写模板时增加测试数据和填充内容还是很有好处的。关于更多ejs语法的问题,请移步这里。
针对单个模板文件,使用ejs可以帮助我们快速生成页面;而针对同个项目的多个模板页面所需要的相同数据,比如相同的CDN路径前缀,我们该怎么处理呢?
跟据"DRY"原则,在每个模板文件上定义相同的变量可不是一件好事,此时,可以使用HtmlWebpackPlugin
插件提供的htmlWebpackPlugin
全局变量。
在HtmlWebpackPlugin
配置项中传入额外的参数,然后再模板上通过 htmlWebpackPlugin.options[key]
的形式,就可以访问到预先在配置文件中定义的变量
// webpack.config.js
new HtmlWebpackPlugin({
// 省略了其他配置
setCDN(url){
return "http://localhost:9999/assets/" + url;
}
}),
// page1.ejs
<img src="<%= htmlWebpackPlugin.options.setCDN('1.png') %>" alt="">
在输出的模板文件上面可以看见<img src="http://localhost:9999/assets/1.png" alt="">
。因此,模板通用的变量啥的都可以这样定义然后调用。需要注意的是,即使不是在ejs文件中使用这种语法也是可以的。
开发环境
在目前的开发过程中一般会经历三个开发环境:
- 本地localhost开发,浏览器热更新
- 本地服务器配置虚拟域名开发,调试相关路由和数据
- 线上生产环境
在不同的开发环境下,需要的输出文件可能是不一样的,这意味着需要频繁改动配置文件,这是一个很容易出问题的活(之前这么搞把配置弄炸了两三次)。更好的做法是使用package.json
的scripts
,通过不同的命令来执行不同的打包配置。呃没错,接下来我们来搭建一个简陋的webpack开发环境。
环境切换
由于可能需要在不同的开发环境来回切换,为了减少对于配置文件的改动,因此使用--env
参数并在配置文件中进行判断,然后导出整个配置文件。
// package.json
"scripts": {
"dev": "webpack --config ./config/webpack.base.js --env.dev",
},
为了更方便的进行设置,将相关的配置都集中在了config.js
文件中,然后在多个文件之间使用process.env.NODE_ENV
判断判断环境
module.exports = (env)=>{
/*==========env==========*/
["dev", "test", "build"].forEach(item=>{
if (env[item]){
process.env.NODE_ENV = item;
}
});
// 其他的配置文件中就可以根据process.env.NODE_ENV来判断对应的环境了
}
文件配置
在config.js
中声明了
- 需要独立打包的样式表,对应
util.style.js
文件 - 模板文件及相关配置,对应
util.tpl.js
文件, - 入口文件,对应
webpack.base.js
文件 - 跟环境相关的配置
源码
由于代码较多,这里将整个项目整理到github上,这里是webpackTpl传送门,在后续的工作过程中应该会逐步更新。
你要请我喝一杯奶茶?
版权声明:自由转载-非商用-保持署名和原文链接。
本站文章均为本人原创,参考文章我都会在文中进行声明,也请您转载时附上署名。