相信 webpack 这个名称对于前端的同学来说并不陌生, 只要你在用 vue , react 等等之类的框架, 就得天天和它打交道. 但是大部分人都只是直接怼一个 vue-cli 脚手架生成一个项目, 运行起来就开始一顿写, 完全不会去看这个项目的其他相关的东西, 今天开始, 咱们就来说说这个又爱又恨的 webpack
问题
使用 wepack 的时候经常会出现下面这些疑问
你 webpack 只能打包单页面的文件吗?
WTF, 我包怎么这么大, 加载太慢了
我打包速度怎么这么慢, 什么破玩意?
...
为什么要使用 webpack
哈, 你问我为什么要用? 因为大家都在用啊: smiley::smiley:. 开个玩笑, 前端发展到今天, 新技术新思想新框架爆发式增长, 当前的浏览器环境跑不赢啊, 你说你写个 ES6/7 在浏览器环境都能跑起来? 扯淡的. 这个时候 babel 就出现了, 你跑不起来是吧, 那我转成 ES5 你总该跑起来吧~, 那 babel 我还是不能直接用啊, 肯定得借助工具编译呀, 所以我们需要 webpack 去做这件事情了. 这个时候有人就要站出来说了, 我 gulp 不服, 我也能做, 我就不用 webpack . 你这么说我就要跟你唠唠了, 现在我们先来比较一下 webpack 和 gulp .
gulp 是 task runner , Webpack 是 module bundler
webpack 的优势在模块化, gulp 除了模块化方面都很不错. 但是前端发展至今, 模块化真的很重要, CMD , AMD 就是模块化的产物.
简单来说, 如果你当前项目需要模块化编程, 那就选 webpack , 如果是处理其他事情, 比如把图片拼接成雪碧图或者压缩, 那么 gulp 是最擅长的
感兴趣的可以看看这个回答 gulp 有哪些功能是 webpack 不能替代的?
安装
这里可以参考 webpack 官网
- > mkdir webpack && cd webpack
- > NPM init -y
- > NPM install webpack webpack-cli -D
入口(entry)
每个 webpack 都会有一个 entry , 就是入口的意思, 指示 webpack 应该使用哪个模块, 来作为构建其内部依赖图的开始.
注意点:
入口可以有多个, 如果是单页面只需要一个入口, 多页面可以设置多个路口
入口的文件必须是 .JS 文件, 因为 webpack 只认识 JS
举个: chestnut:
我们新建 webpack.config.JS 和新建 src 文件夹, 并且文件夹下新建 index.JS 文件
目录如下
- - webpack/
- - src/
- - index.JS
- - webpack.config.JS
webpack.config.JS
- module.exports = {
- entry: './src/index.js'
- };
我们上面指定 webpack 的入口文件为 index.JS 文件, 这是总入口
出口(output)
有入口当然就会有出口了, 就是你导出的文件
webpack.config.JS
- module.exports = {
- entry: './src/index.js',
- output: {
- path: path.resolve(__dirname, 'dist'),
- filename: 'bundle.js'
- }
- };
上述 entry 已经介绍过了, 我们来看看 output , 他有文件导出的路径 (path) 和导出的文件名(filename)
关于 filename 这里需要注意的地方有:
bundle.JS
- filename: '[name].bundle.js'
- module.exports = {
- entry: {
- App: './src/index.js'
- },
- output: {
- path: path.resolve(__dirname, 'dist'),
- filename: 'bundle.js'
- }
- };
现在我们来试一下 webapck 打包
小试牛刀
第一步, 新建一个文件夹 webpack-demo
- > mkdir webpack-demo && cd webpack-demo
- > NPM init -y
- > cnpm install webpack webpack-cli -D
第二步, 新建 src/index.JS 文件和 webpack.config.JS 文件
webpack.config.JS
- const path = require('path');
- module.exports = {
- entry: './src/index.js',
- output: {
- filename: 'bundle.js',
- path: path.resolve(__dirname, 'dist/'),
- },
- };
index.JS
document.write('hello webpack');
第三步, 打包
命令行输入
> npx webpack --config webpack.config.JS
然后控制台就会输出
- Hash: 348dca17387cd3f29cef
- Version: webpack 4.33.0
- Time: 227ms
- Built at: 2019-06-08 15:24:07
- Asset Size Chunks Chunk Names
- bundle.JS 961 bytes 0 [emitted] main
Entrypoint main = bundle.JS
[0] ./src/index.JS 33 bytes {0} [built]
看到这个信息证明你已经大功告成了, 去看看 dist / 文件夹下是不是有打包好的 JS 文件
最后面你会看到有黄色的警告, 说 mode 没有设置, 待会再讲
这个时候你就会想, 我每次生成的文件都叫 bundle.JS, 我都区分不开, 也不好做缓存, 这个时候你就需要配置一下 filename 了
我们把 webpack.config.JS 改成以下
- const path = require('path');
- module.exports = {
- entry: {
- App: './src/index.js',
- },
- output: {
- filename: '[name].[hash].js',
- path: path.resolve(__dirname, 'dist/'),
- },
- };
然后执行
> npx webpack --config webpack.config.JS
这个时候 dist / 文件夹下就会多出个类似 App.32434c7cc602e3049dac.JS 的文件, 而且如果你反复执行打包命令, 你发现 App.32434c7cc602e3049dac.JS 文件名都没有改变, 这是为什么呢?
因为 webpack 会判断你的文件是否有更改而来觉得文件夹 hash 的变更, 现在你可以尝试修改一下 index.JS 文件之后打包的效果就知道了.
模式(mode )
上面说到每次打包的时候都会报警告, 告诉我们没有设置 mode, 现在我们来说说 mode
首先 mode 有两个值, 分别是 development 和 production, 意思就是, 当前项目打包的开发环境还是生成环境的代码
如果你设置了 mode: 'development', 在项目里你可以使用 process.env.NODE_ENV 来获取当前的环境的值
你可以尝试把 webpack.config.JS 改成以下, 然后在 index.JS 里把这个值打印出来, 运行一下效果
webpack.config.JS
- const path = require('path');
- module.exports = {
- mode: 'development',
- entry: {
- App: './src/index.js',
- },
- output: {
- filename: '[name].[hash].js',
- path: path.resolve(__dirname, 'dist/'),
- },
- };
index.JS
document.write(`hello webpack,this is ${process.env.NODE_ENV}`);
现在我们只有 JS 文件, 你可以先在根目录新建一个 index.html 文件, 把 JS 引入在浏览器环境执行(或者直接在浏览器控制台执行 JS), 你会看到浏览器显示 -> hello webpack,this is development
你分别运行之后会发现他们的效果是不一样的, 一个是被压缩的, 一个没有被压缩
这个时候你就会想了, 怎么这么麻烦, 我打包还得自己去新建 HTML 文件引入 JS 然后运行或者去执行 JS 文件, 能不能让他自动运行跑起来?
当然是可以的, 下面我们来说说 plugins
## 插件(plugins)
插件是 webpack 的支柱功能. webpack 自身也是构建于, 你在 webpack 配置中用到的相同的插件系统之上!(官网的解释)
插件怎么说呢? 不好解释它, 你可以理解为处理工具, 插件目的在于解决 loader (这个等会再讲, 现在用不上) 无法实现的其他事
插件怎么配置? 就像下面这样, 当然不是随便找的插件, 我们会用到下面配置的插件
- const path = require('path');
- const HtmlWebpackPlugin = require('html-webpack-plugin');
- module.exports = {
- mode: 'development',
- entry: {
- App: './src/index.js',
- },
- output: {
- filename: '[name].[hash].js',
- path: path.resolve(__dirname, 'dist/'),
- },
- plugins: [
- new HtmlWebpackPlugin({template: 'index.html'})
- ]
- };
我们先在根目录新建一个 index.HTML 文件, 之前不是说运行项目很麻烦嘛? 现在教你简单的方法
然后我们用到了 HTML-webpack-plugin, 需要先安装他才能使用
> cnpm i HTML-webpack-plugin -D
下一步打包运行项目
> npx webpack --config webpack.config.JS
运行结果:
- Hash: bea857ae13cad8af6e66
- Version: webpack 4.33.0
- Time: 274ms
- Built at: 2019-06-08 16:00:33
- Asset Size Chunks Chunk Names
- App.bea857ae13cad8af6e66.JS 3.83 KiB App [emitted] App
- index.HTML 74 bytes [emitted]
Entrypoint App = App.bea857ae13cad8af6e66.JS
- [./src/index.JS] 65 bytes {App} [built]
- Child HTML-webpack-plugin for "index.html":
- 1 asset
Entrypoint undefined = index.HTML
- [./node_modules/_html-webpack-plugin@3.2.0@HTML-webpack-plugin/lib/loader.JS!./index.HTML] 209 bytes {0} [built]
- [./node_modules/_webpack@4.33.0@webpack/buildin/global.JS] (webpack)/buildin/global.JS 472 bytes {0} [built]
- [./node_modules/_webpack@4.33.0@webpack/buildin/module.JS] (webpack)/buildin/module.JS 497 bytes {0} [built]
- + 1 hidden module
去查看 dist 文件夹下, 你会发现多出了两个文件, JS 和 index.HTML 文件, 这就是插件的功劳
HTML-webpack-plugin 这个插件需要指定是那个 HTML 模板, 然后最后打包的时候就是以这个模板为主, 把打包好的 JS 文件放到这个 index.HTML 里面, 你可以查看 HTML 文件里的内容:
index.HTML
<script type="text/javascript" src="app.1b0b2001b0579faec32d.js"></script>
这个时候你会发现, 我靠, 我 dist 文件怎么这么多啊, 怎么办啊? 别急, 我们再来用一个插件解决这个问题
安装插件 clean-webpack-plugin
> cnpm i clean-webpack-plugin -D
然后配置文件去添加插件
- const path = require('path');
- const HtmlWebpackPlugin = require('html-webpack-plugin');
- const { CleanWebpackPlugin } = require('clean-webpack-plugin');
- module.exports = {
- mode: 'development',
- entry: {
- App: './src/index.js',
- },
- output: {
- filename: '[name].[hash].js',
- path: path.resolve(__dirname, 'dist/'),
- },
- plugins: [
- new CleanWebpackPlugin(),
- new HtmlWebpackPlugin({template: 'index.html'})
- ]
- };
然后你再去看看 dist 文件夹里的文件, 是不是只有两个文件了? 这个插件的作用是, 先把 dist 文件夹里的文件先清空然后再把打包好的文件放入 dist.
那么你还会有问题, 这还是麻烦啊, 我不能只运行命令行, 让重新自己打开浏览器运行我打包的项目吗? 当然可以啊
首先安装 webpack-dev-server
> cnpm i webpack-dev-server -D
然后
> webpack-dev-server --open --config webpack.config.JS
你会发现重新自动打开了浏览器, 页面显示 hello webpack,this is development. 是不是很简单?
你现在可以去修改 index.JS 然后保存文件, 去浏览器看看是不是自动刷新了你刚刚更改的内容呢?
现在你可能还会有问题, 我去, 这太简单了吧, 我要用 CSS 和图片怎么办? JS 不能导入 CSS 文件啊! 我怎么跟 vue 一样在自己的 ip 访问项目啊? 现在肯定是问题一大堆
loader
loader 用于对模块的源代码进行转换. loader 可以使你在 import 或 "加载" 模块时预处理文件. 比如可以把 typescript 转换成 JavaScript,Less 转成 CSS
现在我们就来解决你上一章节末的问题, 教你配置简单的 loader, 来加载 CSS 或者图片
首先我们先安装 CSS-loader/style-loader, 来加载解析 CSS 文件
> cnpm i CSS-loader style-loader -D
下一步在 src 文件夹下新建 test.CSS 文件, 再在 index.JS 导入
test.CSS
- body {
- background: #ccc;
- }
index.JS
- import './test.css';
- document.write(`hello webpack,this is ${process.env.NODE_ENV},test`);
如果你直接运行会发现控制台报错了
- ERROR in ./src/test.CSS 1:5
- Module parse failed: Unexpected token (1:5)
- You may need an appropriate loader to handle this file type.
- > body {
- | background: #ccc;
- | }
- @ ./src/index.JS 1:0-20
这个时候 loader 登场了, 我们修改配置文件
webpack.config.JS
- const path = require('path');
- const HtmlWebpackPlugin = require('html-webpack-plugin');
- const { CleanWebpackPlugin } = require('clean-webpack-plugin');
- module.exports = {
- mode: 'development',
- entry: {
- App: './src/index.js',
- },
- output: {
- filename: '[name].[hash].js',
- path: path.resolve(__dirname, 'dist/'),
- },
- plugins: [
- new CleanWebpackPlugin(),
- new HtmlWebpackPlugin({ template: 'index.html' }),
- ],
- module: {
- rules:[
- {
- test: /\.CSS$/,
- use: ['style-loader', 'css-loader'],
- },
- ]
- }
- };
然后运行命令行
> webpack-dev-server --open --config webpack.config.JS
你会发现页面背景颜色变了
现在我们来说说配置:
module.rules 允许你在 webpack 配置中指定多个 loader, 上面我们规定正则匹配 CSS 文件, 然后如果匹配到了, 则使用 style-laoder 和 CSS-loader 去处理 CSS 文件, CSS-laoder 负责解析 CSS 文件, style-loader 负责把 CSS 文件放到页面中去, 你打开调试可以看到 head 里被插入了 style 样式标签, 当前如果你想解析例如 xx.ts 文件, 则可以在数组里面新增:
- {
- test: /\.ts$/,
- use: 'ts-loader',
- },
下面来看看怎么加载图片资源, 还是跟上述原一样, 图片也是有类型的, 我们首先得匹配文件后缀, 然后去用 loader 去解析他们, 这里我们需要用到 url-loader file-loader
按照惯例先安装
- > cnpm i url-loader file-loader -D
- module.exports = {
- ...
- module: {
- rules:[
- {
- test: /\.CSS$/,
- use: ['style-loader', 'css-loader'],
- },
- {
- test: /\.(PNG|jpe?g|gif|svg)(\?.*)?$/,
- loader: 'url-loader',
- options: {
- limit: 6000,
- name: 'img/[name].[hash:7].[ext]',
- },
- }
- ]
- }
- };
下一步就是往项目里增加图片了
我们修改 test.CSS 文件
test.CSS
- body {
- background: url(img.jpg);
- }
浏览器就显示的全是刚刚设置的重复图片了
这里你又会问了, 不对, 你这里只用到了 url-loader,file-loader 不是多余的吗? 不是的, 你可以看看 options, 有一个 limit 参数, 规定如果超过了 6000bytes 大小的文件会交给 file-loader 处理, 因为如果图片小于这个数值, url-loader 会把图片转成 base64 格式的图片加载, 如果超过就自己不处理了, 所以他们两者是有相依性的
使用 NPM 脚本
上面基本上都是使用一大段的命令行来执行项目, 现在我们来简化一下
修改 package.JSON
- "scripts": {
- "dev": "webpack-dev-server --open --config webpack.config.js",
- "build": "webpack --config webpack.config.js"
- },
命令行运行项目
- > NPM run dev
- > NPM run build
- devServer
在开发中你可能有很多需求, 比如怎么通过 ip 访问项目, 怎么把控制台信息输出的精简点, 怎么修改端口等等? 这个时候就需要用到 devServer 的配置了
我们修改 webpack.config.JS, 增加以下:
- module.exports = {
- ...
- devServer: {
- contentBase: './dist', // 告诉服务器从哪里提供内容
- host: '0.0.0.0', // 指定使用一个 host. 默认是 localhost
- useLocalIp: true, // 是否使用本地 ip
- open: true, // 是否自动打开浏览器
- port: 8080, // 端口号
- noInfo: true, // 显示的 webpack 包 (bundle) 信息」的消息将被隐藏
- },
- }
是的, 你现在可以不用在命令行里增加 - open 这个参数, 在这里配置也是一样的
来源: http://www.tuicool.com/articles/FjqqEjJ