webpack学习笔记(二)环境分离+多页面开发配置
2018-04-09 15:26
1416 查看
本章描述的主要功能
postcss-loadernodemon
使用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-pluginjs压缩,压缩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源码
相关文章推荐
- webpack学习笔记-7-开发环境和生产环境
- 使用webpack、babel、react、antdesign配置单页面应用开发环境
- 分离Webpack开发环境与生产环境的配置
- Kinect开发学习笔记之(三)Kinect开发环境配置
- OAF学习笔记-开发环境配置
- 我的Android学习开发笔记-eclipse环境配置
- SharePoint开发学习笔记3——Visual Web Part及自定义配置界面
- IOS开发学习笔记(二十四)——内嵌WebView页面
- Python 学习笔记之配置开发环境
- Sencha Touch 2学习笔记(一)---环境搭建和开发工具配置
- 一步步学习SPD2010--第十三章节--管理SP Server环境的Web内容(9)--分离和重新关联页面布局
- cocos2d-x 3.x游戏开发学习笔记(1)--mac下配置cocos2d-x 3.x开发环境
- coolite1.0 学习笔记(一) -- 配置coolite开发环境
- 一步步学习SPD2010--第十三章节--管理SP Server环境的Web内容(9)--分离和重新关联页面布局
- Python web框架Django学习(1)——在win7 64bit下配置开发环境Django:一个可以使Web开发工作愉快并且高效的Web开发框架。 使用Django,使你能够以最小的代价构建和
- Kinect开发学习笔记之(三)Kinect开发环境配置 (转)
- [J2EE学习笔记01]配置标准的J2EE开发环境
- 学习笔记:java学习第一课 开发工具,环境配置
- Web开发学习笔记之一:如何配置IIS使其能运行ISAPI动态链接库程序?
- Ubuntu 操作系统学习笔记之c/c++开发环境配置