您的位置:首页 > Web前端 > Webpack

webpack学习笔记(二)环境分离+多页面开发配置

2018-04-09 15:26 1416 查看

本章描述的主要功能

postcss-loader

nodemon

使用less/scss/sass

封装资源处理,样式处理,并加入全局config(仿照vue)

生产开发分离

开发环境配置压缩打包

chunk接受

多页面开发原理和配置介绍

代码接着上一章webpack学习笔记(一)从零开始构建基础webpack项目

postcss-loader

postcss-loader介绍 使用
postcss-loader
来为css样式自动加入浏览器前缀,
postcss-loader
的使用需要配合相对应的
postcss.config.js
文件,以及其插件,这里我只使用了
autoprefixer
插件

npm i -D postcss-loader autoprefixer


加入
postcss-loader
,创建
postcss.config.js
文件并写代码

/* webpack.js */
...
{
// 对css文件使用loader
test: /\.css$/,
// 使用插件提取样式
use: ExtractTextPlugin.extract({
// 样式loader运行顺序 加入postcss-loader
use: ['css-loader', 'postcss-loader'],
// 若上述处理进行顺利,执行style-loader并导出文件
fallback: 'style-loader',
// 样式覆盖路径 处理背景图之类
publicPath: '../'
})
}
...

/* postcss.config.js */
module.exports = {
plugins: [
require('autoprefixer')({
browsers: ['last 5 versions']
})
]
};


写一下样式并看效果,这里直接打包生成文件看效果
npm run build




已经加入相关了浏览器前缀

nodemon

先附上nodemon的GitHub介绍,我们用它来监控进程,这样我们就不需要每次修改之后还需要重启node服务,下载安装包,并修改
package.json


npm i -D nodemon


"scripts": {
"start": "nodemon --watch build/ --exec \"webpack-dev-server  --config build/webpack.js\"",
"build": "webpack --config build/webpack.js"
}


我们使用
nodemon
监控
build
这个文件夹,并且开辟一个子进程运行
webpack-dev-server
,然后我们可以先运行
npm start
,之后在运行本地服务的时候修改
build
文件夹下面的文件就不再需要重启服务

使用less/scss/sass

首先下载
loader
,并且修改
.css
loader
,添加对
.less
.scss
.sass
文件的处理,
.scss
.sass
公用一个
loader
,并且执行
sass-loader
需要
node-sass
来配合使用

下载安装包

npm i -D less-loader sass-loader node-sass


增加对应的处理
loader
,这里就先不分离css了,直接在浏览器上看

/* webpack.js */
...
{
// 对less文件使用loader
test: /\.less$/,
use: ['style-loader', 'css-loader', 'postcss-loader', 'less-loader']
},
{
// 对sass文件使用loader
test: /\.sass$/,
use: ['style-loader', 'css-loader', 'postcss-loader', 'sass-loader']
},
{
// 对scss文件使用loader
test: /\.scss$/,
use: ['style-loader', 'css-loader', 'postcss-loader', 'sass-loader']
}
...


创建
.less
.scss
.sass
文件,随便写点什么,然后由
main.js
引入

源码如下



运行结果如下



封装资源处理,样式处理,并加入全局config(仿照vue)

考虑到之后要的分离,不可能每一个环境配置里面都写一遍loader,而且样式的loader基本相似,顶多预处理的loader不一样而已,用代码来生成这些吧

封装处理资源类(img, media)

build
目录下创建
utils.js
webpack.js
引入
utils.js
,并修改图片和媒体文件的
loader
处理

/* utils.js */
const path = require('path');

// 静态资源文件+第三方资源存放文件夹名称
const staticDir = 'static';

// 资源路径 背景图片 bgm等,传入路径以及文件名 ex img/xxx.png
exports.assetsPath = _path => path.posix.join(staticDir, _path);

/* webpack.js */
const utils = require('./utils');
...
{
// 对下列资源文件使用loader
test: /\.(gif|jpg|png|woff|svg|eot|ttf)\??.*$/,
loader: 'url-loader',
options: {
// 小于10kb将会转换成base64
limit: 10240,
// 大于10kb的资源输出地[name]是名字[ext]后缀
name: utils.assetsPath('img/[name].[hash:6].[ext]')
}
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10240,
name: utils.assetsPath('media/[name].[hash:6].[ext]')
}
}
...


这里也就不运行看效果了,只是改了一下执行方法而已~

封装处理样式类

无论怎样封装,最终输出的都必须是对象或类数组对象,类似下面的形式

// 分离样式
{
// xxx.ext
test: /\.xxx$/,
// 使用插件提取样式
use: ExtractTextPlugin.extract({
// 样式loader运行顺序
use: [xx, xx, xx],
fallback: 'style-loader',
// 样式覆盖路径 处理背景图之类
publicPath: '../../'
})
}
// 不分离样式
{
// xxx.ext
test: /\.xxx$/,
use: [xx, xx, xx]
}


第一步,在
build
下创建
4000
一个全局配置的
config.js
文件,之后多页面开发也会用到

/* config.js */
module.exports = {
// 开发
dev: {
// .map文件是否生成
sourceMap: true,
// 是否分离样式
extract: false
},
// 生产
prod: {
sourceMap: false,
extract: true
}
};


重头戏来了,
utils.js
加入样式处理函数,具体代码和功能附上

const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
// 静态资源文件+第三方资源存放文件夹名称
const staticDir = 'static';

// 资源路径 背景图片 bgm等,传入路径以及文件名 ex img/xxx.png
exports.assetsPath = _path => path.posix.join(staticDir, _path);

/**
* 样式处理 生成样式loader对象,并放入数组里面
* @param { Object } options 对应config.js里面的对象属性
* @return { Array }
*/
exports.styleLoader = (options) => {
options = options || {};
// 两个固定的loader
const cssLoader = {
loader: 'css-loader',
options: {
sourceMap: options.sourceMap
}
};
const postcssLoader = {
loader: 'postcss-loader',
options: {
sourceMap: options.sourceMap
}
};
/**
* 生成{test: xxx, use: xxx}中use的值
* @param { String } loader loader名 less, scss, sass, 可为空,空默认css
*/
function generateLoaders(loader) {
// 加入两个固定loader
const loaders = [cssLoader, postcssLoader];
if (loader) {
// 通过名字加入loader,然后从末尾压入数组,感谢loader的执行顺序是从后往前
loaders.push({
loader: `${loader}-loader`,
options: {
// 是否生成.map
sourceMap: options.sourceMap
}
});
}
// 是否需要提取样式
if (options.extract) {
return ExtractTextPlugin.extract({
use: loaders,
fallback: 'style-loader',
publicPath: '../../'
});
}
return ['style-loader'].concat(loaders);
}
// 通过上述函数生成对应的值
const loaders = {
css: generateLoaders(),
less: generateLoaders('less'),
scss: generateLoaders('sass'),
sass: generateLoaders('sass')
};
// console.log(loaders);
// 打印结果如下
// {
//     css:
//     ['style-loader',
//         { loader: 'css-loader', options: [Object] },
//         { loader: 'postcss-loader', options: [Object] }],
//     less:
//     ['style-loader',
//         { loader: 'css-loader', options: [Object] },
//         { loader: 'postcss-loader', options: [Object] },
//         { loader: 'less-loader', options: [Object] }],
//     scss:
//     ['style-loader',
//         { loader: 'css-loader', options: [Object] },
//         { loader: 'postcss-loader', options: [Object] },
//         { loader: 'sass-loader', options: [Object] }],
//     sass:
//     ['style-loader',
//         { loader: 'css-loader', options: [Object] },
//         { loader: 'postcss-loader', options: [Object] },
//         { loader: 'sass-loader', options: [Object] }]
// }
const output = [];
// 遍历并加入正则,得到最终的loader
for (const key in loaders) {
const loader = loaders[key];
// console.log(new RegExp(`\\.${key}$`));
output.push({
test: new RegExp(`\\.${key}$`),
use: loader
});
}
return output;
// console.log(JSON.stringify(output));
// 运行结果如下 test其实是有值的,上述打印可见
// [
//     {
//         "test": {},
//         "use": ["style-loader", { "loader": "css-loader", "options": {} }, { "loader": "postcss-loader", "options": {} }]
//     },
//     {
//         "test": {},
//         "use": ["style-loader", { "loader": "css-loader", "options": {} }, { "loader": "postcss-loader", "options": {} }, { "loader": "less-loader", "options": {} }]
//     },
//     {
//         "test": {},
//         "use": ["style-loader", { "loader": "css-loader", "options": {} }, { "loader": "postcss-loader", "options": {} }, { "loader": "sass-loader", "options": {} }]
//     },
//     {
//         "test": {},
//         "use": ["style-loader", { "loader": "css-loader", "options": {} }, { "loader": "postcss-loader", "options": {} }, { "loader": "sass-loader", "options": {} }]
//     }
// ]
};


webpack.js
中使用这个方法,删除原有的样式
loader
处理方法,并在
loaders
数组中通过
...utils.styleLoader()
,附上目前
webpack.js
代码

/* webpack.js */
// 路径解析
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const rm = require('rimraf');
const utils = require('./utils');

// 删除
rm(path.join(__dirname, '../dist'), (err) => {
if (err) throw err;
});

module.exports = {
// js文件入口
entry: path.join(__dirname, '../src/script/main.js'),
// 输出到dist目录
output: {
path: path.join(__dirname, '../dist'),
filename: 'static/js/[name].[hash].js'
},
// devServer: {
//     host: '192.168.0.101',
//     port: '2018'
// },
module: {
loaders: [
...utils.styleLoader(),
{
// 对js文件使用loader
test: /\.js$/,
use: 'babel-loader'
},
{
test: /\.html$/,
loader: 'html-loader',
options: {
// 标签+属性
attrs: ['img:src', 'audio:src', 'video:src']
}
},
{
// 对下列资源文件使用loader
test: /\.(gif|jpg|png|woff|svg|eot|ttf)\??.*$/,
loader: 'url-loader',
options: {
// 小于10kb将会转换成base64
limit: 10240,
// 大于10kb的资源输出地[name]是名字[ext]后缀
name: utils.assetsPath('img/[name].[hash:6].[ext]')
}
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10240,
name: utils.assetsPath('media/[name].[hash:6].[ext]')
}
}
]
},
plugins: [
new ExtractTextPlugin('static/css/[
f534
name].[hash].css'),
new HtmlWebpackPlugin({
// 生成的html文件名,该文件将被放置在输出目录
filename: 'index.html',
// 源html文件路径
template: path.join(__dirname, '../src/page/index.html')
}),
new CopyWebpackPlugin([{
// 源文件目录
from: path.join(__dirname, '../static'),
// 目标目录 dist目录下
to: 'static',
// 筛选过滤,这里复制所有文件,连同文件夹
ignore: ['.*']
}])
]
};


环境分离

一般工作中都会建立两个环境,开发环境,生成环境,话不多说,先在
build
下面创建两个环境文件再说,开发
webpack.dev.js
,生产
webpack.prod.js
,然后修改
package.json
中的运行命令

/* package.json */
"scripts": {
"start": "nodemon --watch build/ --exec \"webpack-dev-server  --config build/webpack.dev.js\"",
"build": "webpack --config build/webpack.prod.js"
}


基础配置

在之前的
webpack.js
中有一些配置不需要分离出去,如
.js
.html
,图片和媒体资源的处理,
html-webpack-plugin
的配置,
copy-webpack-plugin
等这些基础的配置,开发和生产环境可以共用,其它配置需要分离出去,删除配置之后的代码如下

/* webpack.js */
// 路径解析
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const utils = require('./utils');

module.exports = {
// js文件入口
entry: path.join(__dirname, '../src/script/main.js'),
// 输出到dist目录
output: {
path: path.join(__dirname, '../dist'),
filename: 'static/js/[name].[hash].js'
},
// devServer: {
//     host: '192.168.0.101',
//     port: '2018'
// },
module: {
loaders: [
{
// 对js文件使用loader
test: /\.js$/,
use: 'babel-loader'
},
{
test: /\.html$/,
loader: 'html-loader',
options: {
// 标签+属性
attrs: ['img:src', 'audio:src', 'video:src']
}
},
{
// 对下列资源文件使用loader
test: /\.(gif|jpg|png|woff|svg|eot|ttf)\??.*$/,
loader: 'url-loader',
options: {
// 小于10kb将会转换成base64
limit: 10240,
// 大于10kb的资源输出地[name]是名字[ext]后缀
name: utils.assetsPath('img/[name].[hash:6].[ext]')
}
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10240,
name: utils.assetsPath('media/[name].[hash:6].[ext]')
}
}
]
},
plugins: [
new HtmlWebpackPlugin({
// 生成的html文件名,该文件将被放置在输出目录
filename: 'index.html',
// 源html文件路径
template: path.join(__dirname, '../src/page/index.html')
}),
new CopyWebpackPlugin([{
// 源文件目录
from: path.join(__dirname, '../static'),
// 目标目录 dist目录下
to: 'static',
// 筛选过滤,这里复制所有文件,连同文件夹
ignore: ['.*']
}])
]
};


webpack-merge

先附上介绍webpack-merge 使用这个包,我们可以合并对象或数组,以及函数,这样我们就可以在开发和生产环境的代码中引入基础配置,用这个包来合并两者,达到环境的构建

npm i -D webpack-merge


暂停一下

写这篇博客的时候,发现自己漏了sourceMap的生成,按照上一篇的配置,即便在
config.js
里面设置
sourceMap: true
也不会生成
.map
文件,原因是我忘记在webpack配置里面加入
devtool: 'inline-source-map'
(开发),
devtool: 'source-map'
(生产)

开发环境

在开发环境中,我们并需要压缩代码,也不需要分离样式(不过部分移动端手机浏览器不分离样式就没法加载样式),只需开启本地服务,进行项目调试,引入
webpack.js
通过
webpack-merge
进行添砖加瓦

const baseWebpack = require('./webpack');
const merge = require('webpack-merge');
const config = require('./config');
const utils = require('./utils');
// 部分移动端浏览器不提取样式无法被加载
// const ExtractTextPlugin = require('extract-text-webpack-plugin');

module.exports = merge(baseWebpack, {
devtool: 'inline-source-map',
module: {
loaders: utils.styleLoader(config.dev)
}
// 先保留一下
// plugins: [
//     new ExtractTextPlugin('static/css/style.css')
// ]
});


生产环境

生产环境中,我们需要删除
dist
文件夹,需要压缩css和js代码,先下载需要两个包
uglifyjs-webpack-plugin
js压缩,压缩css我们只需要改动
utils.js
(ps:写这片博客前我用的是
optimize-css-assets-webpack-plugin
来压缩的,按照
loader
的执行顺序,只需要在
css-loader
加入
minimize
属性即可,然后整理这篇文章的时候发现这样也可以压缩)

npm install -D uglifyjs-webpack-plugin


/* webpack.prod.js */
const path = require('path');
const baseWebpack = require('./webpack');
const merge = require('webpack-merge');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const config = require('./config');
const rm = require('rimraf');
const utils = require('./utils');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');

// 删除
rm(path.join(__dirname, '../dist'), (err) => {
if (err) throw err;
});

module.exports = merge(baseWebpack, {
devtool: 'source-map',
module: {
loaders: utils.styleLoader(config.prod)
},
plugins: [
new ExtractTextPlugin('static/css/[name].[hash].css'),
new UglifyJsPlugin({
// 是否生成.map
sourceMap: config.prod.sourceMap
})
]
});


打包然后浏览器打开
dist
下面的
index.html
。js和css都已被压缩,另外我添加了一个
name.js




浏览器打开结果(sourceMap生效了)



多页面开发

前提条件chunk

如果不了解chunk,即便是我们能生成多个
html
文件,但其所引用的js文件始终是同一个,这样就达不到多页面开发的要求,毕竟a页面不需要b页面的某些东西。。。

这里简单说一下chunk,我们现在在创建
entry
的时候只提供了一个入口文件
main.js
,如果我们添加多个入口文件,在
src/script/
创建
demo1.js
demo2.js
然后我们修改
entry
的写法,这些入口文件就是chunk,其
chuankName
entry
的键名,也就是之前一直使用
[name]
的值

/* webpack.js */
entry: {
index: path.join(__dirname, '../src/script/main.js'),
demo1: path.join(__dirname, '../src/script/demo1.js'),
demo2: path.join(__dirname, '../src/script/demo2.js')
}


直接打包生成文件看一下
npm run build




可以看到生成了index, demo1, demo2这些js文件,不再是之前的main名字的形式,说明我们的设置的chuankName生效了,但是我们的
index.html
只想要index.js这个文件,怎么弄呢,别着急,
html-webpack-plugin
插件早已提供的方法,其提供了一个
chunks
属性,值为一个数组,里面放入需要提取的入口文件的chuankName即可

/* webpack.js */
new HtmlWebpackPlugin({
// 生成的html文件名,该文件将被放置在输出目录
filename: 'index.html',
// 只提取chuankName为index的入口文件打包生成的js
chunks: ['index'],
// 源html文件路径
template: path.join(__dirname, '../src/page/index.html')
})


继续打包一下
npm run build
,只引入了一个js



加入一个新的html文件,我们创建
demo1.html
,并且使用
html-webpack-plugin
来处理它,让它所对应的入口文件为
demo1.js


/* webpack.js */
new HtmlWebpackPlugin({
// 生成的html文件名,该文件将被放置在输出目录
filename: 'demo1.html',
chunks: ['demo1'],
// 源html文件路径
template: path.join(__dirname, '../src/page/demo1.html')
})


继续打包



了解了上述原理,多页面开发的配置就一目了然,我们需要创建入口文件
entry
对象,还需要配置html文件的HtmlWebpackPlugin插件的数组,并且为了统一配置,我们需要保持
entry
对象的键名和html文件的文件名相同,直白一点就是我们创建一个html的时候,需要同时创建一个同名的js文件,并且在
entry
对像中注册登记,键名就是其文件名,举个栗子如下

/* 栗子 */
module.exports = {
/* 四个入口文件 */
entry: {
/* 键名 文件名保持一致 */
index: path.join(__dirname, '../src/script/index.js'),
demo1: path.join(__dirname, '../src/script/demo1.js'),
demo2: path.join(__dirname, '../src/script/demo2.js'),
demo3: path.join(__dirname, '../src/script/demo3.js')
},
....
plugins: [
/* 所对应的html文件 */
new HtmlWebpackPlugin({
filename: 'index.html',
chunks: ['index'],
template: path.join(__dirname, '../src/page/index.html')
}),
new HtmlWebpackPlugin({
filename: 'demo1.html',
chunks: ['demo1'],
template: path.join(__dirname, '../src/page/demo1.html')
}),
new HtmlWebpackPlugin({
filename: 'demo2.html',
chunks: ['demo2'],
template: path.join(__dirname, '../src/page/demo2.html')
}),
new HtmlWebpackPlugin({
filename: 'demo3.html',
chunks: ['demo3'],
template: path.join(__dirname, '../src/page/demo3.html')
})
]
};


看完上面的栗子,所谓的多页面开发无非就是通过简单的代码生成上述栗子中的
entry
对象
HtmlWebpackPlugin
数组,而且我们要求名称一致,这就便利了我们来通过代码生成这些配置

撸起袖子加油干

通过代码生成对象和数组

/* webpack.js */
// 所有多页面的文件名
const PageName = ['index', 'demo1', 'demo2'];
// entry对象
const Entries = {};
// 插件数组
const HtmlPlugins = [];
// 生成
PageName.forEach((page) => {
const htmlPlugin = new HtmlWebpackPlugin({
filename: `${page}.html`,
template: path.join(__dirname, `../src/page/${page}.html`),
chunks: [page]
});
HtmlPlugins.push(htmlPlugin);
Entries[page] = path.join(__dirname, `../src/script/${page}.js`);
});
/* 配置代码 省略了很多0.0 */
module.exports = {
// js文件入口
entry: Entries,
plugins: [
...HtmlPlugins,
]
};


到这里时候我们需要改一下入口文件的名字,之前我们一直使用的是
main.js
,现在我们需要将其改为
index.js
,运行一下



html,js一一对应的被生成了,我们只需要手动输入html文件名就能进行多页面,不过为了规范,我们之前创建了一个全局对象
config.js
,我们可以将这PageName的值写到
config.js
里面,然后在
webpack.js
里面调用

/* config.js */
module.exports = {
// 多页面配置
pageNames: [
'index',
'demo1',
'demo2'
],
// 开发
dev: {
sourceMap: true,
extract: false
},
// 生产
prod: {
sourceMap: true,
extract: true
}
};

/* webpack.js */
const config = require('./config');
config.pageNames.forEach((page) => {
....
});


如果进行多页面开发,就自行创建对应文件并在
config.js
内注册,如果不使用
nodemon
就需要重启node

基础的多页面开发的原理以及配置就说到这里了,下一章的笔记将会描述如何创建模版,提取公共chuank~

依旧附上本章GitHub源码
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐