webpack是什么
打包工具(模块打包器)
前端工程师,必不可少工具
webpack作用
- 打包 (把多个文件打包成一个js文件, 较少服务器压力、带宽)
- 转化 (比如less、sass、ts) 需要loader
- 优化 (SPA越来越盛行,前端项目复杂度高, webpack可以对项目进行优化)
安装webpack
npm install webpack webpack-cli webpack-dev-server -g
- 验证webpack环境已经ok?
webpack -v
webpack配置文件(webpack.config.js)
module.exports={
//入口配置
entry:{},
//出口配置
output:{},
//module.rules
//loaders
module:{},
//插件
plugins:[],
//开发服务器
devServer:{}
};
配置文件名字一定得叫 webpack.config.js ?
答: 不是
如果改名叫mmr.config.js,运行时候:webpack --config mmr.config.js
package.json:
"scripts": {
"build": "webpack --config mmr.config.js",
"dev":"xxx",
"aaa":"xxx"
}
- Entry(入口)
绘制依赖关系图的起始文件被称为entry。默认的entry为 ./src/index.js,或者我们可以在配置文件中配置。entry可以为一个也可以为多个。
单个entry:
module.exports = {
entry: './src/index.js'
}
或者
module.exports = {
entry: {
main: './src/index.js'
}
};
- 多个entry,一个chunk
我们也可以指定多个独立的文件为entry,但将它们打包到一个chunk中,我们需要传入文件路径的数组:
module.exports = {
entry: ['./src/index.js', './src/index2.js', './src/index3.js']
}
但是改种方法的灵活性和扩展性有限,因此并不推荐使用。
- 多个entry,多个chunk
如果有多个entry,并且每个entry生成对应的chunk,我们需要传入object:
module.exports = {
entry: {
app: './src/app.js',
admin: './src/admin.js'
}
};
这种写法有最好的灵活性和扩展性,支持和其他的局部配置进行合并。比如将开发环境和生产的配置分离,并抽离出公共的配置,在不同的环境下运行时再将环境配置和公共配置进行合并。
- Output(出口)
有了入口,对应的就有出口。顾名思义,出口就是webpack打包完成的输出,output定义了输出的路径和文件名称。Webpack的默认的输出路径为 ./dist/main.js。同样,我们可以在配置文件中配置output:
module.exports = {
entry: './src/index.js',
output: {
path: __dirname + '/dist',
filename: 'bundle.js'
}
};
- 多个entry的情况
当有多个entry的时候,一个entry应该对应一个output,此时输出的文件名需要使用替换符(substitutions)声明以确保文件名的唯一性,例如使用入口模块的名称:
module.exports = {
entry: {
app: './src/app.js',
search: './src/search.js'
},
output: {
filename: '[name].js',
path: __dirname + '/dist'
}
}
最终在 ./dist 路径下面会生成 app.js和search.js两个bundle文件。
- Loader
Webpack自身只支持加载js和json模块,而webpack的理念是让所有的文件都能被引用和加载并生成依赖关系图,所以loader出场了。Loader能让webpack能够去处理其他类型的文件(比如图片、字体文件、xml)。我们可以在配置文件中这样定义一个loader:
module.exports = {
module: {
rules: [
{
test: /\.txt$/,
use: 'raw-loader'
}
]
}
};
其中test定义了需要被转化的文件或者文件类型,use定义了对该文件进行转化的loader的类型。该条配置相当于告诉webpack当遇到一个txt文件的引用时(使用require或者import进行引用),先用raw-loader转换一下该文件再把它打包进bundle。
还有其他各种类型的loader,比如加载css文件的css-loader,加载图片和字体文件的file-loader,加载html文件的html-loader,将最新JS语法转换成ES5的babel-loader等等。
-
Plugin(插件)
Plugin和loader是两个比较混淆和模糊的概念。Loader是用来转换和加载特定类型的文件,所以loader的执行层面是单个的源文件。而plugin可以实现的功能更强大,plugin可以监听webpack处理过程中的关键事件,深度集成进webpack的编译器,可以说plugin的执行层面是整个构建过程。Plugin系统是构成webpack的主干,webpack自身也基于plugin系统搭建,webpack有丰富的内置插件和外部插件,并且允许用户自定义插件。官方列出的插件有 这些。与loader不同,使用plugin我们必须先引用该插件,例如:
const webpack = require('webpack'); // 用于引用webpack内置插件
const HtmlWebpackPlugin = require('html-webpack-plugin'); // 外部插件
module.exports = {
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
})
]
};
实践
了解webpack的基本概念之后,我们通过实践来加深理解。接下来,我们使用webpack搭建一个简易的vue-cli脚手架。
- 创建一个vue项目
使用命令行mkdir vuedeom 或者直接新建一个vuedemo空文件夹,然后命令行cd vuedemo,使用npm init -y
初始化,此时你会看到文件夹多了一个package.json的文件,内容大致如下:
{
"name": "vuedemo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
- 引入webpack
通过npm加载webpack,输入命令:npm install webpack webpack-cli --save-dev
,接下来我们在项目根目录创建一个src文件,创建一个main.js,再创建一个webpack.config.js,最后修改package.json的脚本:
const path = require('path') //引入node内置模块path
module.exports ={
entry:'./src/main.js', // 入口文件,把src下的main.js编译到出口文件
output:{ //出口文件
path:path.resolve(__dirname,'dist'), //出口路径和目录
filename:"demo.js" //编译后的名称
}
}
//package.json
{
"name": "vuedemo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build":"webpack" //当使用npm run build的时候就会执行webpack,按照提示安装webpack-cli
},
"keywords": [],
"author": "",
"license": "ISC"
}
- babel编译
虽然ES6语法已经广泛普及,但各个浏览器还不是特别兼容,为了避免出错我们需要把ES6转成ES5,使用babel进行编译
npm install --save-dev babel-core babel-loader
加载完成之后,在webpack.config.js配置
const path = require('path')
module.exports ={
entry:'./src/main.js',
output:{
path:path.resolve(__dirname,'dist'),
filename:"demo.js"
},
module:{
rules:[ //遍历规则
{
test: /\.js$/, //匹配以js结尾的文件
loader:"babel-loader", // 使用babel-loader编译
exclude: /node_modules/ //node_module里面的内容不遍历
}
]
}
}
我测试的时候出现了这样的错误,如果有相同情况的可以参考下:
Error: Cannot find module ‘@babel/core’
babel-loader@8 requires Babel 7.x (the package ‘@babel/core’). If you’d like to use Babel 6.x (‘babel-core’), you should install ‘babel-loader@7’.
这是因为版本之间的不兼容,按照上面的要求,你可以安装低版本的babel-loader@7
`npm i -D babel-loader@7`
也有可能webpack会发出这样的警告:
>WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
你需要给脚本配置一个环境,一般build我们会用生产环境webpack --mode production,而dev会使用生产环境webpack --mode development,
另外,有时候我们可能会遇到不能识别webpack命令,原因未知,不过重新安装一次就可以了...
接下来需要让babel-loader翻译官具有翻译的功能:
`npm install babel-preset-es2015 --save-dev`
并且新建一个.babelrc的文件,里面新建
```
{ "presets":["es2015"] }
```
如果需要转译ES7语法,你还需要安装
`npm install babel-preset-stage-0 --save-dev`
同样在.babelrc添加
```
{
"presets":["es2015"."stage-0"]
}
```
每次修改配置之后都要重新编译:npm run build
- 解析样式
上面我们实现了vue引入和es6以及es7语法转译,现在我们来解析样式,需要安装两个包
npm install css-loader style-loader --save-dev
css-loader将css解析成模块,style-loader将解析的内容插入到style标签内
别忘了在配置里webpack.config.json添加规则
rules:[
//....省略
{
test:/\.css$/,
use:['style-loader','css-loader']
}
]
但是大多数时候,我们在vue中会使用样式预处理语言,比如sass、less、stylus,同样地我们需要安装对应的包,添加对应的规则
npm install less less-loader --save-dev
rules:[
//....省略
{
test:/\.less$/,
use:['style-loader','css-loader','less-loader']
}
]
- 解析图片
图片是大多数项目不可获取的部分,怎样解析图片呢?和解析样式步骤差不多,我们需要先安装包再添加规则
npm install file-loader url-loader --save-dev
rules:[
//....省略
{
test:/\.(jpg|png|gif)$/,
use:'url-loader?limit=8192'
},
{
test:/\.(eot|svg|woff|woff2|wtf)$/,
use:'url-loader'
}
]
limit表示转化base64只在8192字节一下转化,其他情况输出图片
- 解析html
我们希望build之后能有一个html文件,能直接看到编译之后的效果
这时就需要一个插件,插件的作用是以我们自己的html为模板将打包后的结果,自动引入到html中产出到dist目录下
npm install html-webpack-plugin --save-dev
在webpack.config.js引入这个插件
let HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
//....省略
plugins:[new HtmlWebpackPlugin({ //自动插入到dist目录中
template:'./src/index.html' //使用模板
filename:'index.html' //产出名称(一般不写)
})]
}
build之后你就可以看到dist下有一个index.html文件
-
开发环境
一个项目创建分为开发环境和生产环境(上线),那么在开发的时候每次都需要build很不方便,而且build之后相当于最终的代码,不能随意更改,我们需要把这些内容都放到内存中,通过npm run dev
打开
npm install webpack-dev-server --save-dev
这里边内置了服务,可以帮我们启动一个端口号,当代码更新时,自动在内存中打包,代码有变化就重新执行
并且在package.json添加一个新脚本:"dev":"webpack-dev-server --mode development"
一般webpack-dev-server会内置一个端口,通过这个端口就能查看编译的内容了,比如我的端口号:http://localhost:8080 -
配置vue
- 在src 新建
App.vue
文件,代码如下:
<template>
<div id="main">
Hello Word
</div>
</template>
<script>
</script>
<style>
</style>
上面我们已经实现了基本的webpack配置,完成了html、css、less、图片、js等文件的解析,但我们最终想要的适合vue-cli一样的效果,这就要求我们还要对vue语法进行解析
通过安装vue-loader(解析.vue文件)和vue-template-compiler(解析template模板)实现以.vue文件加载到html文件
npm install vue-loader vue-template-compiler --save-dev
在webpack.config.js配置
rules:[
//....省略
{
test: /\.vue$/, //压缩vue
use: [{
loader: "vue-loader" //压缩vue的loader
}],
exclude: '/node_modules/' // 排除压缩的文件
}
]
我们在main.js引入App.vue模块,然后在render()引用:render:(h)=>h(App)
import Vue from 'vue'
import App from './App.vue'
new Vue({
render: h => h(App),
}).$mount('#app')
之后执行npm run dev
会报错:
webpack vue-loader was used without the corresponding plugin. Make sure to include VueLoaderPlugin
解决办法:
在webpack.config.js引入这个插件
//....省略
const VueLoaderPlugin = require('vue-loader/lib/plugin');
module.exports = {
//....省略
plugins:[
//....省略
new VueLoaderPlugin()
]
}
到这里我们就实现了和vue-cli初始化出来的vue项目一样的效果,其实整个过程并不算太难
最终webpack.config.js配置
const path = require('path') //引入node内置模块path
let HtmlWebpackPlugin = require('html-webpack-plugin')
const VueLoaderPlugin = require('vue-loader/lib/plugin');
module.exports ={
entry:'./src/main.js', // 入口文件,把src下的main.js编译到出口文件
output:{ //出口文件
path:path.resolve(__dirname,'dist'), //出口路径和目录
filename:"demo.js" //编译后的名称
},
module:{
rules:[ //遍历规则
{
test: /\.js$/, //匹配以js结尾的文件
loader:"babel-loader", // 使用babel-loader编译
exclude: /node_modules/ //node_module里面的内容不遍历
},
{
test:/\.css$/,
use:['style-loader','css-loader']
},
{
test:/\.less$/,
use:['style-loader','css-loader','less-loader']
},
{
test:/\.(jpg|png|gif)$/,
use:'url-loader?limit=8192'
},
{
test:/\.(eot|svg|woff|woff2|wtf)$/,
use:'url-loader'
},
{
test: /\.vue$/, //压缩vue
use: [{
loader: "vue-loader" //压缩vue的loader
}],
exclude: '/node_modules/' // 排除压缩的文件
}
]
},
plugins:[
new HtmlWebpackPlugin({ //自动插入到dist目录中
template:'./src/index.html', //使用模板
filename:'index.html' //产出名称(一般不写)
}),
new VueLoaderPlugin()
]
}
最终package.json:
{
"name": "vuedemo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "webpack",
"dev": "webpack-dev-server --mode development"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"babel-core": "^6.26.3",
"babel-loader": "^7.1.5",
"babel-preset-es2015": "^6.24.1",
"babel-preset-stage-0": "^6.24.1",
"css-loader": "^3.4.2",
"file-loader": "^6.0.0",
"html-webpack-plugin": "^4.0.1",
"less": "^3.11.1",
"less-loader": "^5.0.0",
"style-loader": "^1.1.3",
"url-loader": "^4.0.0",
"vue": "^2.6.11",
"vue-loader": "^15.9.1",
"vue-template-compiler": "^2.6.11",
"webpack": "^4.42.1",
"webpack-cli": "^3.3.11",
"webpack-dev-server": "^3.10.3"
}
}