1 webpack使用说明
webpack是目前流行的前端模块化打包工具,通过丰富的插件和扩展功能,可以高效的实现前端构建,简化工作量。webpack的核心部分实现了js的模块化方案,每一个js文件作为一个模块,在构建阶段进行打包。而webpack的 加载器(loader) 和 插件(plugins) 提供了更多的功能,使webpack可以处理几乎所有的前端资源,如css文件,html文件以及图片等静态资源。
本文档基于webpack2.x版本编写,webpack2x和webpac1x有比较明显的改变。按照此文档说明操作前请确认正在使用的是webpack2.x版本。
2 webpack安装以及环境搭建
2.1 node环境
webpack依赖于nodejs环境,因此在使用webpack前确保已经安装了nodejs。
2.2 安装webpack
在命令行中使用npm安装webpack,采用全局模式:
npm install webpack -g
2.3 运行方式
有3种常用的运行webpack打包的方式。首先打开node命令行工具,进入到项目目录下:
2.3.1 使用默认配置文件
默认情况下webpack会在项目目录下寻找webpack.config.js文件作为配置文件。在命令行中输入:
webpack
此时webpack使用webpack.config.js文件作为配置文件运行。
2.3.2 指定配置文件
webpack允许使用自定义的配置文件。为了区分开发环境和生产环境,我们可以分别创建两个配置文件webpack.config.dev.js(开发)和webpack.config.pro.js(生产).
使用开发环境的配置文件webpack.config.dev.js
webpack --config webpack.config.dev.js
使用生产环境的配置文件webpack.config.pro.js
webpack --config webpack.config.pro.js
2.3.3 npm scripts
当命令行中需要输入的指令较多时,比如:
webpack --config widget/webpack.config.pro.js process.env.NODE_ENV="'production'"
为了简化输入过程,以及避免输入错误,我们可以利用npm scripts命令执行构建。
首先在项目目录下创建package.json文件:
npm init
在文件中找到”scripts”字段,并设置构建命令:
{
// ...
"scripts": {
"build": "webpack --config widget/webpack.config.pro.js process.env.NODE_ENV="'production'""
},
// ...
}
在命令行中运行:
npm run build
以上两种方法的效果完全一致。
2.4 配置文件简单示例
webpack.config.js文件:
var path = require('path');
module.exports = {
// 入口文件
entry: './app/index.js',
// 输出文件
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
// 模块文件的处理声明
module: {
// ...
},
// 模块文件别名
resolve: {
// ...
},
// 插件
plugins: [
// ...
]
};
3 webpack在F1平台的使用
平台webpack结构图:
3.1 区分生产环境和开发环境
webpack可以设置不同的配置文件,只需在运行webpack时通过参数给出配置文件:
webpack --config 配置文件路径及文件名
单独建立开发环境和生产环境的配置文件,可以提高开发效率和质量。在两种模式中,存在下列不同的需求:
—— | 开发环境 | 生产环境 |
---|---|---|
自动编译及刷新 | 是 | 否 |
source-map | 是 | 否 |
代码混淆 | 否 | 是 |
文件版本号 | 否 | 是 |
开发环境的侧重点是提升调试的效率,生产环境的侧重点是提升代码在浏览器运行的性能。
目前,F1平台的前端UI组件库使用webpack进行打包。组件库资源在public_libraries 和 widget_libraries中。其中public_libraries文件夹中是平台组件依赖的第三方库文件,widget_libraries文件夹中是组件库的源码及静态资源。项目目录如下:
public_libraries:
目录名 | 内容 |
---|---|
vendor | 平台依赖的第三方库文件 |
dist | 打包后输出的文件 |
webpack-config | webpack各配置项的设置 |
widget_libraries:
目录名 | 内容 |
---|---|
src | 平台组件js源码 |
stylus | 平台样式文件源码 |
dist | 打包后输出目录 |
webpack-config | webpack各配置项的设置 |
webpack打包平台组件库时,我们要处理的资源文件按类型分为:
打包过程分为两步:
- 打包第三方库文件
- 打包平台组件代码
下面详细说明不同类型文件的处理方式。
3.2 第三方库文件
第三方库文件的特点是不属于平台源码,且修改频率较低。因此理想情况是三方代码和组件代码独立打包,当组件代码修改时,只需要重新打包组件代码而不需打包三方代码。这样可以提高打包的效率,同时在浏览器端可以充分利用缓存机制减少页面请求资源的时间。基于以上的考虑,我们选择webpack提供的dll方案处理三方文件(另一种官方提供的方案是CommonsChunkPlugin)。
dll方案的思路是webpack对三方库文件单独做一次打包,生成dll.bundle.js文件和dll-manifest.json文件,该文件是组件库依赖的文件。之后打包组件库文件时需要声明依赖的dll文件。实现dll方案需要两个插件,DllPlugin和DllReferencePlugin。
现在我们把lib下的文件从原目录中提取出来,并建立一个新的文件夹public_libraries,如下图:
vendor和原来的lib内容相同,包含所有第三方库文件。webpack-config是webpack的配置文件,dist是打包生成的文件。
3.2.1 DllPlugin配置
webpack.vendor.config.js
三方库文件配置:
module.exports = {
//...
output: {
//...
library: '[name]_lib' // 输出文件声明为库文件
},
plugins: [
// DllPlugin插件
new webpack.DllPlugin({
context: '',
path: path.resolve(dirVars.dllDir, './dll-manifest.json'), // 输出manifest文件的绝对路径
name: 'dll_lib', // 库文件的名字,和output.library一致
})
]
}
使用dll插件后,webpack打包后会生成一个manifest.json文件,该文件是库文件dll.bundle.js的map:
webpack.widget.config.js
组件库配置:
module.exports = {
//...
plugins: [
// DllReferencePlugin插件
new webpack.DllReferencePlugin({
context: path.resolve(dirVars.staticRootDir, '../'),
manifest: path.resolve(dirVars.dllDir, './dll-manifest.json'),// manifest文件路径
name: 'dll_lib'
})
]
}
3.2.2 全局变量声明
webpack打包时会把js文件的作用域限定在本文件以内,即文件中的全局变量在其他文件中无效。但是对于第三方库如jquery、moment等,我们有时需要以全局变量的形式使用(兼容过去的代码或是在没有模块化的环境中)。想要在webpack生成的文件中暴露出库文件的全局变量,我们需要利用’expose-loader’。
‘expose-loader’用于把某个模块的作用域提升至全局。首先通过npm安装:
npm i expose-loader --save
在配置文件的module中使用’expose-loader’声明全局模块:
module: {
rules: [
{
test: require.resolve(dirVars.vendorDir + '/jquery/jquery-1.10.2.js'),// test指定模块路径
use: ['expose-loader?$', 'expose-loader?jQuery']// 全局变量名字是$和jQuery
},
{
test: require.resolve(dirVars.vendorDir + '/moment/moment.js'),
use: ['expose-loader?moment']
}
]
}
全局变量现在还是无效的,我们还需要在其他文件中通过require()的方式引用jquery和moment,这样全局变量才会生效。在vendor目录下的init.vendor.js文件的作用就是引用所有声明为全局模块的第三方库文件。在组件使用webpack打包时会使用这个文件。
3.2.3 模块间通用变量
根据js模块化规范,一个模块想要引用另一个模块必须显示的引用,比如require或import。但是对于jquery这样的基础库,每次都require() 显然会降低效率。通过 ProvidePlugin可以很好的解决这个问题。在此之前,我们先设置配置文件中的resolve.alias属性:
module.exports = {
resolve: {
alias: {
// 设置jquery-1.10.2.js模块的别名为jquery
jquery: path.resolve(dirVars.vendorDir + '/jquery/jquery-1.10.2.js')
}
}
}
设置别名后,我们可以这样引用jquery模块:
require('jquery');
只需要在require中写’jquery’,而不用写完整的路径。但是还不够方便,我们希望不用require,当模块中有$或jQuery时,自动引用jquery模块。这时就需要使用ProvidePlugin插件:
plugins: [
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery'
})
]
$('#item');
jQuery('#item');
此时会自动引用jquery模块。
3.2.4 第三方库文件打包总结
打包后,我们在dist目录下得到dll.bundle.js和dll-manifest.json两个文件。使用三方库文件需要在html页面中引用dll.bundle.js文件,同时还要引用组件库文件打包的结果(后面会有详细说明)。dll-manifest.json文件作为引用关系说明在组件库打包时使用。
3.3 平台js源码
平台依赖的三方库文件单打包,而js源码、stylus文件和其他静态资源一起打包。分离三方库文件后平台组件库的项目目录如下:
3.3.1 dll库依赖
首先,我们应该在配置文件中声明对三方库dll文件的依赖,需要使用DllReferencePlugin:
plugins: [
// DllReferencePlugin中的属性值应该和三方库打包的DllPlugin中的属性值对应
new webpack.DllReferencePlugin({
context: path.resolve(dirVars.staticRootDir, '../'),
manifest: path.resolve(dirVars.dllDir, './dll-manifest.json'),// manifest文件路径
name: 'dll_lib'
})
]
3.3.2 全局变量和模块间通用变量声明
在平台的组件代码以及业务代码中,有一些全局变量被广泛使用,比如F1Map,F1DataTable,DateFormat。为了兼容原有代码,我们需要把这些变量设置为模块间通用的变量和全局变量。声明方式和三方库文件一致,使用ProvidePlugin和expose-loader。
设置模块别名:
resolve: {
alias: {
F1Map: path.resolve(dirVars.srcDir + '/util/jquery.f1.map.js'),
}
}
声明模块间通用变量:
plugins: [
new wepack.ProvidePlugin({
F1Map: 'F1Map',
})
]
声明全局变量:
module: {
rules: [
{
test: path.resolve(dirVars.srcDir + '/util/jquery.f1.map.js'),
use: ['expose-loader?F1Map']
}
]
}
3.4 stylus文件处理
平台组件的样式文件源码使用stylus编写。打包后的样式文件应该实现以下需求:
1. stylus文件编译为css文件。
2. css代码与js代码分离,生成单独的css样式文件。
3. 样式文件的输出路径可以指定。
4. 输出文件的路径改变后,样式文件中引用的静态资源路径自动变化,比如背景图片和字体图标。
3.4.1 样式文件编译
webpack默认把所有引用的资源作为js文件处理,所以对于stylus、css文件我们需要通过加载器(loader)处理。在这里使用’stylus-loader’,’css-loader’,’style-loader’。配置项设置如下:
module: {
rules: [
{
test: /\.styl$/,
use: [
{loader: 'style-loader'},
{loader: 'css-loader'},
{loader: 'stylus-loader'}
]
}
]
}
编译后得到的css代码与js代码在同一个文件里,以
3.4.2 css代码分离
为了分离css代码,我们引入’extract-text-webpack-plugin’插件。它能帮助我们生成单独的css样式文件。
var extractTextPlugin = require('extract-text-webpack-plugin');
// 创建提取器实例,参数是输出文件的路径及文件名,用于处理平台蓝色主题的样式文件
var extractStylusBlue = new extractTextPlugin('css/blue/[name].bundle.css');
module.exports = {
...
module: {
rules: [
{
test: /\.styl$/, // 正则,匹配文件模块
include: [
path.resolve(dirVars.stylusDir, 'blue') // 限定目录,缩小匹配范围
],
// 使用文件提取器处理stylus文件
use: extractStylusBlue.extract({
use: [{
loader: 'css-loader',
options: {
sourceMap: true
}
}, {
loader: 'stylus-loader'
}],
// 设置输出文件基础路径,默认和js文件输出在同一个目录
publicPath: '../../'
})
}
]
},
plugins: [
extractStylusBlue // 注意在插件中加入提取器,否则不起作用
]
}
利用loader和’extract-text-webpack-plugin’插件可以成功编译打包平台组件库中的样式文件。生成文件如下图所示:
3.5 其他静态资源
目前为止看,我们已经解决了第三库文件、js源码和stylus样式文件的编译打包问题,还剩下的就是处理项目中的其他静态资源。和stylus文件一样,我们需要增加新的加载器(loader),这里要用到’file-loader’,’url-loader’。
3.5.1 图片
图片使用’url-loader’:
module: {
rules: [
{
test: /\.(png|jpg|gif)$/, // 匹配图片扩展名
use: {
loader: 'url-loader', // 加载器声明
options: {
limit: 8192, // 小于8192kb的图片以base64编码的形式转换为元数据,不生成独立的图片文件
name: 'image/[name].[ext]' //name中加入路径用于解决样式文件中url路径错误的问题
}
}
}
]
}
3.5.2 字体图标
字体图标使用’file-loader’:
module: {
rules: [
{
test: /\.(eot|svg|ttf|woff)$/, // 匹配图片扩展名
use: {
loader: 'file-loader', // 加载器声明
options: {
name: 'fonts/[name].[ext]' //name中加入路径用于解决样式文件中url路径错误的问题
}
}
}
]
}
打包后生成文件如下:
3.6 组件库使用
至此平台所用的全部资源文件都可以通过webpack进行处理,并在dist目录中输出。组件库打包后,输出目录dist内容如下:
css是平台的样式文件,fonts是字体图标,images是图片,scripts是js文件。如果我们想要在页面中使用平台组件库,只需在页面中引用这些文件就可以了,就像以前引用jquery文件一样。注意不要忘记引用平台依赖的三方库。下图是引用示例:
<!-- 组件库样式文件 -->
<link rel="stylesheet" type="text/css" href="/jquery/css/blue/widget.bundle.css">
<!-- 平台组件依赖的三方库 -->
<script type="text/javascript" src="/public/dll.bundle.js"></script>
<!-- 平台组件库文件 -->
<script type="text/javascript" src="/jquery/scripts/widget.bundle.js"></script>
<!-- 当前页面业务代码 -->
<script type="text/javascript" src="/sysconfig/scripts/syscode.js"></script>