首页 > 技术文章 > webpack学习笔记

moran1992 2018-03-07 17:21 原文

安装搭建node环境

下载node地址:https://nodejs.org/en/download/ 

测试环境是否安装成功,安装成功之后执行命令可以看到node的版本号。

1 node -v//查看node版本

查看npm版本

1 npm -v//查看npm版本

现在准备环境做好,开始webpack之旅。

首先利用npm生成一个package.json文件,这里生成一个默认的就可以。

1 npm init -y

安装之后的package.json

 1 {
 2   "name": "webpacktest",
 3   "version": "1.0.0",
 4   "description": "",
 5   "main": "index.js",
 6   "scripts": {
 7     "test": "echo \"Error: no test specified\" && exit 1"
 8   },
 9   "keywords": [],
10   "author": "",
11   "license": "ISC"
12 }

安装webpack

安装webpack 有两种方式,一种是全局安装

1 npm install -g webpack

一种是依赖安装,将webpack信息依赖到package.json中

1 npm install --save-dev webpack

这里采用第二种安装方式,安装之后会发现在package.json中的变化,package.json中多了webpack的信息。

 1 {
 2   "name": "webpacktest",
 3   "version": "1.0.0",
 4   "description": "",
 5   "main": "index.js",
 6   "scripts": {
 7     "test": "echo \"Error: no test specified\" && exit 1"
 8   },
 9   "keywords": [],
10   "author": "",
11   "license": "ISC",
12   "devDependencies": {
13     "webpack": "^4.1.0"
14   }
15 }

要基于react,所以还要安装一些必要的包

1 npm install react react-dom babel-core babel-loader babel-preset-es2015 babel-preset-react --save-dev

安装之后的package.json

 1 {
 2   "name": "webpacktest",
 3   "version": "1.0.0",
 4   "description": "",
 5   "main": "index.js",
 6   "scripts": {
 7     "test": "echo \"Error: no test specified\" && exit 1"
 8   },
 9   "keywords": [],
10   "author": "",
11   "license": "ISC",
12   "devDependencies": {
13     "babel-core": "^6.26.0",
14     "babel-loader": "^7.1.4",
15     "babel-preset-es2015": "^6.24.1",
16     "babel-preset-react": "^6.24.1",
17     "react": "^16.2.0",
18     "react-dom": "^16.2.0",
19     "webpack": "^4.1.0"
20   }
21 }

查看webpack版本号

1 webpack -v

执行结果

这里应该注意的是webpack在4.0.0以后cli这个包单独分出来了,需要重新安装一下,执行命令

1 npm install webpack-cli -D

然后查看版本号

 发现还是不行,解决方法:npm i -g webpack-cli -D --save(来自stackoverflow)

 开始进入正题配置webpack.config.js

在webpacktest文件夹下创建一个名为webpack.config.js的文件

 1 const path = require('path');
 2 module.exports = {
 3     entry: path.resolve(__dirname, './index.js'),          
 4     //entry: "./index.js",
 5     devtool: "source-map",
 6     output: {
 7         path: path.resolve(__dirname, './buildfolder'),
 8         filename: 'bundle.js'
 9     },
10     module: {
11         rules: [
12             {
13                 test: /\.(js|jsx)$/,
14                 use: {
15                     loader: 'babel-loader',
16                     options: {
17                         presets: ['es2015', 'react'],
18                     }
19                 },
20                 exclude: /node_modules/
21             }
22         ]
23     }
24 }

entry:

1     //entry: './entryFile/index.js',
2     //entry: {
3     //    bundle: './entryFile/index.js',
4     //    aui: './entryFile/aui.js'
5     //},
6     //entry: [],

(1)entry: ""

入口以字符串的形式,这种入口文件只能是一个文件,当工程量比较大或者多页面应用程序的时要避免所有文件打到一个文件下,这时产生了(2)及(3)方案

(2)entry: {}

这种方式比较主流,对象方便扩展,这里可以专门写一个函数来实现对象的动态生成,最终达到入口文件可以方便添加的目的。

我们单独设置一个json文件来存放入口文件的文件名跟文件路径

文件名:buildList.json

 1 [
 2   {
 3     "path": "./entryFile/",
 4     "files": [
 5       "index.js"
 6     ]
 7   },
 8   {
 9     "path": "./entryFile/",
10     "files": [
11       "aui.js"
12     ]
13   }
14 ]

在webpack.config.js中将json文件引入

1 var buildList = require('./JSON/buildList.json');

编写入口文件动态生成函数

 1 function buildListParser() {
 2     var
 3         entryObj = {};
 4     for (var i = 0; i < buildList.length; i++) {
 5         var
 6             folder = buildList[i].path,
 7             files = buildList[i].files;
 8         files.forEach(function (value, index, array) {
 9             var
10                 name = value.split('.')[0];
11             entryObj[name] = folder + value;
12         });
13     }
14     return entryObj;
15 };

最终我们的entry可以写成

1  entry: buildListParser(),

(3)entry: []

当存在多个入口的时候我们还可以利用Array的形式,比如第三方库bootstrap,最终bootstrap会被追加到打包好的index.js中,数组中的最后一个会被export。

output:

1 output: {
2         path: path.resolve(__dirname, './dist/'),
3         //filename: 'bundle.js',
4         //filename: '[name].js',
5         //filename: '[name].[chunkhash].js'
6         //filename: '[name].[hash].js'
7     },

(1)filename: 'bundle.js',

对于单个入口文件打包后生成bundle.js文件,这种方式为最简答的配置方式,当入口文件为多个文件时报错。

(2)filename: '[name].js',

这种配置形式会根据entry对象的key进行mapping,打包后输出的文件名会根据entry对象的key进行命名

(3)filename: '[name].[chunkhash].js',

打包后输出的文件会以hash命名的方式生成

(4) filename: '[name].[hash].js'.

打包后输出的文件会以hash命名的方式生成

 

hash与chunkhash的区别:

从打包结果可以看出。hash生成的多个文件,hash值是一样的,而chunkhash值是每个文件不一样的,这样导致的结果就是当打包文件过多时,要用chunkhash,不要使用hash,非要使用就用[name]区分不同输出文件。

loader:

loader有很多babel-loader、less-loader、css-loader等等,我们这里简单loader实现对less的支持。

首先需要通过npm安装

1 npm install less-loader css-loader style-loader less --save-dev

这里需要注意的是less必须安装的,不然打包的时候是会报错滴~

module修改

1    module: {
2         rules: [
3             {
4                 test: /\.less$/,
5                 loader: "style-loader!css-loader!less-loader"
6             }
7         ]
8     },

 

在目标文件引用

1 import '../style/index.less';

这样就能达到我们的项目对less文件的支持。

plugins:

文档中说过,是解决loader无法解决的问题,是webpack的支柱功能,这里结合处理输出文件来演示

webpack打包在使用hash时,hash是动态变化的,这导致每次浏览器读取文件时都会重新加载而不是从内存中读取影响效率,所以要做的是,什么时候hash可以变什么时候不能变?产品或者项目的前端部分,通常由第三方类库,common组件以及业务逻辑组成,业务逻辑在迭代开发过程中是更改最频繁的,此时可以做到只打包业务逻辑部分而不动第三方类库以及common组件部分是最理想的选择,那么如何实现。要了解webpack本身处理模块间依赖关系的,其内置了一个js模板出来处理依赖关系,这段js也同样会被打包到bundle.js中,这会导致每次打包整个文件都会重新生成,在实际项目中,我们要将这段代码分离出来,plugins可以实现将处理模块关系这部分js分离出来。

1  plugins: [
2         //new webpack.NamedModulesPlugin(),
3         //new webpack.optimize.CommonsChunkPlugin({
4         //    name: 'vendor'
5         //}),
6         new webpack.optimize.CommonsChunkPlugin({
7             name: 'runtime'
8         })
9     ],

处理模块间关系的js即runtime,打包之后的效果

 

这样就处理模块之间关系的代码分离出来了。

第三方类库分离

入口文件修改

 1 function buildListParser() {
 2     var
 3         entryObj = {};
 4     for (var i = 0; i < buildList.length; i++) {
 5         var
 6             folder = buildList[i].path,
 7             files = buildList[i].files;
 8         files.forEach(function (value, index, array) {
 9             var
10                 name = value.split('.')[0];
11             entryObj[name] = folder + value;
12         });
13     }
14     entryObj['vendor'] = ['react', 'react-dom'];//declare the third party class libraries
15     return entryObj;
16 };

plugins修改

1   plugins: [
2         //new webpack.NamedModulesPlugin(),
3         new webpack.optimize.CommonsChunkPlugin({
4             name: 'vendor'
5         }),
6         new webpack.optimize.CommonsChunkPlugin({
7             name: 'runtime'
8         })
9     ],

我们看下打包之前的index.hash.js文件

可以看出第三方类库的代码在index中

分离之后然后我们重新打包,效果

可以看到多生成了一个文件名为vendor.hash.js的文件,用来存放第三方类库,打开这个文件可以看到类库的代码。

而index.hash.js中我们发现第三方的代码已经没有了。达到目的~

留个疑问?

在原有模块中添加新的依赖模块

1 import React from 'react';
2 import ReactDOM from 'react-dom';
3 import HomePage from '../Component/Home';
4 import bar from './bar';//new component
5 ReactDOM.render(<HomePage />, document.getElementById('app'));

加了一个bar前后打包结果

加bar之前:

加bar之后:

几种文件hash变化:index变化因为内容变化,runtime变化因为依赖文件多了一个,vendor并没有发生变化,我们可以理解为第三方库没有变化。

但是网上看到一个现象就是vendor也发生变化了,解释原因:

默认情况下webpack的模块都是有id的,多了一个模块导致第三方类库的模块顺序发生变化最终导致vendor也发生变化。

对模块命名NamedModulePlugin以及HashedModuledsPlugin的使用

NamedModulePlugin

上面打包可以看到文件会被具体的分配一个索引

索引变成了相对路径,正如webpack文档说的,当开启NMR的时候使用该插件会显示模块的相对路径,建议开发环境使用

HashedModuledsPlugin

打包后生成文件以hash值为命名

这时产生一个问题就是如何引用这些以hash命名的文件呢,最基本的引用具体入口文件好像已经不管用了,毕竟hash会变,这时html-webpack-plugin出现了。

html-webpack-plugin

修改plugin

 1   plugins: [
 2         //new webpack.NamedModulesPlugin(),
 3         new webpack.HashedModuleIdsPlugin(),
 4         new webpack.optimize.CommonsChunkPlugin({
 5             name: 'vendor'
 6         }),
 7         new webpack.optimize.CommonsChunkPlugin({
 8             name: 'runtime'
 9         }),
10         new HtmlWebpackPlugin()
11     ],

打包后会在dist下生成一个index.html文件

 1 <!DOCTYPE html>
 2 <html>
 3 <head>
 4     <meta charset="UTF-8">
 5     <title>Webpack App</title>
 6 </head>
 7 <body>
 8     <script type="text/javascript" src="runtime.1ded2843a9bceaf2d315.js"></script>
 9     <script type="text/javascript" src="vendor.64e5c5250761125073d7.js"></script>
10     <script type="text/javascript" src="index.12e2b16ab2460153c8a6.js"></script>
11     <script type="text/javascript" src="aui.049898ec5e3cab9e90bd.js"></script>
12 </body>
13 </html>

可以看到已经将hash命名的文件自动引进了生成的index.html中

可以传参数来控制生成的index文件具体参考webpack文档。

当自动生成的index.html不能满足的时候,可以通过模板生成index.html

模板:

 1 <!DOCTYPE html>
 2 <html>
 3 <head>
 4     <meta charset="UTF-8">
 5     <title>Web Api</title>
 6 </head>
 7 <body>
 8     <div id="app"></div>
 9     <!--<script src="./dist/bundle.js"></script>-->
10 </body>
11 </html>

plugins:

 1     plugins: [
 2         //new webpack.NamedModulesPlugin(),
 3         new webpack.HashedModuleIdsPlugin(),
 4         new webpack.optimize.CommonsChunkPlugin({
 5             name: 'vendor'
 6         }),
 7         new webpack.optimize.CommonsChunkPlugin({
 8             name: 'runtime'
 9         }),
10         new HtmlWebpackPlugin({
11             title: 'Web Api',
12             template: __dirname + '/index.html'
13             //filename: 'custom name'
14         })
15     ],

打包后生成的index.html

 1 <!DOCTYPE html>
 2 <html>
 3 <head>
 4     <meta charset="UTF-8">
 5     <title>Web Api</title>
 6 </head>
 7 <body>
 8     <div id="app"></div>
 9     <!--<script src="./dist/bundle.js"></script>-->
10     <script type="text/javascript" src="runtime.1ded2843a9bceaf2d315.js">
11     </script>
12     <script type="text/javascript" src="vendor.64e5c5250761125073d7.js">
13     </script>
14     <script type="text/javascript" src="index.12e2b16ab2460153c8a6.js">
15     </script>
16     <script type="text/javascript" src="aui.049898ec5e3cab9e90bd.js">
17     </script>
18 </body>
19 </html>

如何单独提取css可以使用extract-text-webpack-plugin。

最后一个简单的webpack.config.js

 1 var path = require('path');
 2 var webpack = require('webpack');
 3 var buildList = require('./JSON/buildList.json');
 4 var HtmlWebpackPlugin = require('html-webpack-plugin');
 5 var ExtractTextPlugin = require('extract-text-webpack-plugin');
 6 function buildListParser() {
 7     var
 8         entryObj = {};
 9     for (var i = 0; i < buildList.length; i++) {
10         var
11             folder = buildList[i].path,
12             files = buildList[i].files;
13         files.forEach(function (value, index, array) {
14             var
15                 name = value.split('.')[0];
16             entryObj[name] = folder + value;
17         });
18     }
19     entryObj['vendor'] = ['react', 'react-dom'];//declare the third party class libraries
20     return entryObj;
21 };
22 module.exports = {
23     entry: buildListParser(),
24     //entry: './entryFile/index.js',
25     //entry: {
26     //    bundle: './entryFile/index.js',
27     //    aui: './entryFile/aui.js'
28     //},
29     //entry: [],
30     output: {
31         path: path.resolve(__dirname, './dist/'),
32         //filename: 'bundle.js',
33         //filename: '[name].js',
34         filename: '[name].[chunkhash].js'
35         //filename: '[name].[hash].js'
36     },
37 
38     devtool: "source-map",
39     module: {
40         rules: [
41             {
42                 test: /\.(js|jsx)$/,
43                 exclude: /node_modules/,
44                 use: {
45                     loader: 'babel-loader',
46                     options: {
47                         presets: ['es2015', 'react']
48 
49                     }
50                 }
51             },
52             //{
53             //    test: /\.less$/,
54             //    loader: "style-loader!css-loader!less-loader"
55             //},
56             {
57                 test: /\.less$/,
58                 loader: ExtractTextPlugin.extract({
59                     fallback: "style-loader",
60                     use: [
61                         "css-loader",
62                         "less-loader"
63                     ]
64                 })
65             }
66         ]
67     },
68     plugins: [
69         //new webpack.NamedModulesPlugin(),
70         new webpack.HashedModuleIdsPlugin(),
71         new webpack.optimize.CommonsChunkPlugin({
72             name: 'vendor'
73         }),
74         new webpack.optimize.CommonsChunkPlugin({
75             name: 'runtime'
76         }),
77         new ExtractTextPlugin('./style/index[chunkhash].less'),
78         new HtmlWebpackPlugin({
79             title: 'Web Api',
80             template: __dirname + '/index.html',
81             //hash: true,
82             inject: true,
83             //filename: 'custom name'
84         }),
85     ],
86     resolve: {
87         extensions: [".js", ".jsx", ".json", ".css"],
88     }
89 }

很多地方未涉及,以后分开详细记录。

推荐阅读