1. 认识 webpack
1. 什么是Webpack?
什么是webpack
?
我们先看看官方的解释:
At its core, webpack is a static module bundler for modern JavaScript applications.
从本质上来讲,webpack
是一个现代的JavaScript
应用的静态模块打包工具
。
我们从两个点来解释上面这句话:模块
和 打包
2. 前端模块化
前端模块化
:
目前使用前端模块化的一些方案:AMD
、CMD
、CommonJS
、ES6
在ES6
之前,我们要想进行模块化开发,就必须借助于其他的工具,让我们可以进行模块化开发
并且在通过模块化开发完成了项目后,还需要处理模块间的各种依赖
,并且将其进行整合打包
而webpack
其中一个核心就是让我们可能进行模块化开发,并且会帮助我们处理模块间的依赖
关系
而且不仅仅是JavaScript
文件,我们的CSS
、图片
、json
文件等等在webpack
中都可以被当做模块
来使用
这就是webpack
中模块化
的概念
打包如何理解呢?
理解了webpack
可以帮助我们进行模块化,并且处理模块间的各种复杂关系后,打包的概念就非常好理解了。
就是将webpack
中的各种资源模块进行打包
合并成一个或多个包(Bundle
)
并且在打包的过程中,还可以对资源进行处理,比如压缩图片,将scss
转成css
, 将ES6
语法转成ES5
语法,将TypeScript
转成JavaScript
等等操作。
3. 和grunt/gulp的对比
grunt/gulp
的核心是Task
我们可以配置一系列的task
,并且定义task
要处理的事务(例如ES6
、ts转化
,图片压缩
,scss
转成css
)
之后让grunt/gulp
来依次执行这些task
,而且让整个流程自动化
所以grunt/gulp
也被称为前端自动化任务管理工具
我们来看一个gulp
的task
下面的task
就是将src
下面的所有js
文件转成ES5
的语法
并且最终输出到dist
文件夹中
什么时候用grunt/gulp
呢?
如果你的工程模块依赖非常简单,甚至是没有用到模块化的概念。
只需要进行简单的合并、压缩,就使用grunt/gulp
即可。
但是如果整个项目使用了模块化管理,而且相互依赖非常强,我们就可以使用更加强大的webpack
了。
所以,grunt/gulp
和webpack
有什么不同呢?
grunt/gulp
更加强调的是前端流程的自动化
,模块化不是它的核心
webpack
更加强调模块化开发管理
,而文件压缩合并、预处理等功能,是他附带的功能
const gulp = require ( 'gulp' ) ;
const babel = require ( 'gulp-babel' ) ;
gulp. task ( 'js' , ( ) =>
gulp. src ( 'src/*.js' )
. pipe ( babel ( {
presets: [ 'es2015' ]
} ) )
. pipe ( gulp. dest ( 'dist' ) )
) ;
2. webpack 安装
安装webpack
首先需要安装Node.js
,Node.js
自带了软件包管理工具npm
查看自己的node
版本:
node - v
npm install webpack@3.6 .0 - g
局部安装webpack
--save-dev
是开发时依赖,项目打包后不需要继续使用的。
cd 对应目录
npm install webpack@3.6 .0 -- save- dev
为什么全局安装后,还需要局部安装呢?
在终端直接执行webpack
命令,使用的全局安装的webpack
当在package.json
中定义了scripts
时,其中包含了webpack
命令,那么使用的是局部webpack
3. webpack 起步
1. 准备工作
我们创建如下文件和文件夹:
文件和文件夹解析:
dist
文件夹:用于存放之后打包的文件
src
文件夹:用于存放我们写的源文件
main.js
:项目的入口文件
。具体内容查看下面详情。
mathUtils.js
:定义了一些数学工具函数
,可以在其他地方引用,并且使用。具体内容查看下面的详情。
index.html
:浏览器打开展示的首页html
package.json
:通过npm init
生成的,npm
包管理的文件
mathUtils.js
文件中的代码:
function add ( num1, num2) {
return num1 + num2
}
function mul ( num1, num2) {
return num1 * num2
}
module. exports = {
add,
mul
}
const math = require ( './mathUtils' )
console. log ( 'Hello Webpack' ) ;
console. log ( math. add ( 10 , 20 ) ) ;
console. log ( math. mul ( 10 , 20 ) ) ;
2. js文件的打包
现在的js
文件中使用了模块化
的方式进行开发,他们可以直接使用吗?不可以。
因为如果直接在index.html
引入这两个js
文件,浏览器并不识别其中的模块化代码。
另外,在真实项目中当有许多这样的js
文件时,我们一个个引用非常麻烦,并且后期非常不方便对它们进行管理。
我们应该怎么做呢?使用webpack
工具,对多个js
文件进行打包。
我们知道,webpack
就是一个模块化的打包工具,所以它支持我们代码中写模块化,可以对模块化的代码进行处理。
另外,如果在处理完所有模块之间的关系后,将多个js
打包到一个js
文件中,引入时就变得非常方便了。
如何打包呢?使用webpack
的指令即可
webpack src/ main. js dist/ bundle. js
3. 使用打包后的文件
打包后会在dist
文件下,生成一个bundle.js
文件
文件内容有些复杂,这里暂时先不看,后续再进行分析。
bundle.js
文件,是webpack
处理了项目直接文件依赖后生成的一个js
文件,我们只需要将这个js
文件在index.html
中引入即可
< body>
< script src = " ./dist/bundle.js" > </ script>
</ body>
4. webpack 配置
1. 入口和出口
如果每次使用webpack
的命令都需要写上入口和出口作为参数,就非常麻烦,有方法可以将这两个参数写到配置中,在运行时,直接读取
就是创建一个webpack.config.js
文件
const path = require ( 'path' )
module. exports = {
entry: './src/main.js' ,
output: {
path: path. resolve ( _dirname, 'dist' ) ,
filename: 'bundle.js'
}
}
2. 局部安装webpack
目前,我们使用的webpack
是全局的webpack
,如果我们想使用局部来打包呢?
因为一个项目往往依赖特定的webpack版本
,全局的版本可能很这个项目的webpack版本
不一致,导出打包出现问题。
所以通常一个项目,都有自己局部的webpack
。
第一步,项目中需要安装自己局部的webpack
这里我们让局部安装webpack3.6.0
Vue CLI3
中已经升级到webpack4
,但是它将配置文件隐藏了起来,所以查看起来不是很方便
npm install webpack@3.6 .0 -- save- dev
第二步,通过node_modules/.bin/webpack
启动webpack
打包
node_modules/ . bin/ webpack
3. package.json中定义启动
但是,每次执行都敲这么一长串有没有觉得不方便呢?
我们可以在package.json
的scripts
中定义自己的执行脚本
{
"name" : "meetwebpack" ,
"version" : "1.0.0" ,
"description" : "" ,
"main" : "index.js" ,
"scripts" : {
"build" : "webpack"
} ,
"author" : "" ,
"license" : "ISC" ,
"devDependencies" : {
"webpack" : "^3.6.0"
}
}
package.json
中的scripts
的脚本在执行时,会按照一定的顺序寻找命令对应的位置
首先,会寻找本地的node_modules/.bin
路径中对应的命令。
如果没有找到,会去全局的环境变量中寻找。
如何执行我们的build
指令呢?
npm run build
5. css-loader 使用
1. 什么是loader?
loader
是webpack
中一个非常核心的概念。
webpack
用来做什么呢?
在我们之前的实例中,我们主要是用webpack
来处理我们写的js
代码,并且webpack
会自动处理js
之间相关的依赖。
但是,在开发中我们不仅仅有基本的js
代码处理,我们也需要加载css
、图片
,也包括一些高级的将ES6
转成ES5
代码,将TypeScript
转成ES5
代码,将scss
、less
转成css
,将.jsx
、.vue
文件转成js
文件等等。
对于webpack
本身的能力来说,对于这些转化是不支持的。
那怎么办呢?给webpack
扩展对应的loader
就可以啦。
loader
使用过程:
步骤一:通过npm
安装需要使用的loader
步骤二:在webpack.config.js
中的modules
关键字下进行配置
大部分loader
我们都可以在webpack
的官网中找到,并且学习对应的用法。
2. css文件处理 - 准备工作
项目开发过程中,我们必然需要添加很多的样式,而样式我们往往写到一个单独的文件中。
在src
目录中,创建一个css
文件,其中创建一个normal.css
文件。
我们也可以重新组织文件的目录结构,将零散的js
文件放在一个js
文件夹中。
normal.css
中的代码非常简单,就是将body
设置为red
body {
background-color : red;
}
但是,这个时候normal.css
中的样式会生效吗?
不会,因为我们压根就没有引用它。
webpack
也不可能找到它,因为我们只有一个入口,webpack
会从入口开始查找其他依赖的文件。
在入口文件中引用:
const math = require ( './js/mathUtils' )
console. log ( 'Hello Webpack' ) ;
console. log ( math. add ( 10 , 20 ) ) ;
console. log ( math. mul ( 10 , 20 ) ) ;
require ( './css/normal.css' )
3. css文件处理 – 打包报错信息
重新打包,会出现如下错误:
这个错误告诉我们:加载normal.css
文件必须有对应的loader
4. css文件处理 – css-loader
在webpack
的官方中,我们可以找到如下关于样式的loader
使用方法:
按照官方配置webpack.config.js
文件
注意:配置中有一个style-loader
,我们并不知道它是什么,所以可以暂时不进行配置。
重新打包项目:
但是,运行index.html
,你会发现样式并没有生效。
原因是css-loader
只负责加载css
文件,但是并不负责将css
具体样式嵌入到文档中。
这个时候,我们还需要一个style-loader
帮助我们处理。
5. css文件处理 – style-loader
npm install -- save- dev style- loader
注意:style-loader
需要放在css-loader
的前面。
疑惑:按照我们的逻辑,在处理css
文件过程中,应该是css-loader
先加载css
文件,再由style-loader
来进行进一步的处理,为什么会将style-loader
放在前面呢?
答案:这次因为webpack
在读取使用的loader
的过程中,是按照从右向左
的顺序读取的。
目前,webpack.config.js
的配置如下:
const path = require ( 'path' )
module. exports = {
entry: './src/main.js' ,
output: {
path: path. resolve ( _dirname, 'dist' ) ,
filename: 'bundle.js'
} ,
module: {
rules: [
{
test: /\.css$/ ,
use: [ 'style-loader' , 'css-loader' ]
}
]
}
}
6. less 文件处理
1. less文件处理 – 准备工作
如果我们希望在项目中使用less
、scss
、stylus
来写样式,webpack
是否可以帮助我们处理呢?
我们还是先创建一个less
文件,依然放在css
文件夹中
special.less
@fontSize : 50px;
@fontColor : red;
body {
color : @fontColor ;
font-size : @fontSize ;
}
const math = require ( './js/mathUtils' )
console. log ( 'Hello Webpack' ) ;
console. log ( math. add ( 10 , 20 ) ) ;
console. log ( math. mul ( 10 , 20 ) ) ;
require ( './css/normal.css' )
require ( './css/special.less' )
document. writeln ( '<div>Hello World</div>' )
2. less文件处理 – less-loader
继续在官方中查找,我们会找到less-loader
相关的使用说明
首先,还是需要安装对应的loader
注意:我们这里还安装了less
,因为webpack
会使用less
对less
文件进行编译
npm install -- save- dev less- loader less
{
test: /\.less$/ ,
use: [ {
loader: "style-loader"
} , {
loader: "css-loader"
} , {
loader: "less-loader"
} ]
}
7. 图片文件处理
1. 图片文件处理 – 资源准备阶段
首先,我们在项目中加入两张图片:
一张较小的图片test01.jpg
(小于8kb
),一张较大的图片test02.jpeg
(大于8kb
)
待会儿我们会针对这两张图片进行不同的处理
我们先考虑在css
样式中引用图片的情况,所以我更改了normal.css
中的样式:
body {
background-color : red;
background : url(../imgs/test01.jpeg)
}
如果我们现在直接打包,会出现如下问题
2. 图片文件处理 – url-loader
图片处理,我们使用url-loader
来处理,依然先安装url-loader
npm install -- save- dev url- loader
{
test: /\.(png|jpg|gif|jpeg)$/ ,
use: [
{
loader: 'url-loader' ,
options: {
limit: 8192
}
}
]
}
再次打包,运行index.html
,就会发现我们的背景图片选出了出来。
而仔细观察,你会发现背景图是通过base64
显示出来的
这也是limit
属性的作用,当图片小于8kb
时,对图片进行base64
编码
3. 图片文件处理 – file-loader
那么问题来了,如果大于8kb
呢?我们将background
的图片改成test02.jpg
这次因为大于8kb
的图片,会通过file-loader
进行处理,但是我们的项目中并没有file-loader
所以,我们需要安装file-loader
npm install -- save- dev file- loader
再次打包,就会发现dist
文件夹下多了一个图片文件
4. 图片文件处理 – 修改文件名称
我们发现webpack
自动帮助我们生成一个非常长的名字
这是一个32位hash值
,目的是防止名字重复
但是,真实开发中,我们可能对打包的图片名字有一定的要求
比如,将所有的图片放在一个文件夹中,跟上图片原来的名称,同时也要防止重复
所以,我们可以在options
中添加上如下选项:
img
:文件要打包到的文件夹
name
:获取图片原来的名字,放在该位置
hash
:8
:为了防止图片名称冲突,依然使用hash
,但是我们只保留8位
ext
:使用图片原来的扩展名
options: {
limit: 8192 ,
name: 'img/[name].[hash:8].[ext]'
}
但是,我们发现图片并没有显示出来,这是因为图片使用的路径不正确
默认情况下,webpack
会将生成的路径直接返回给使用者
但是,我们整个程序是打包在dist
文件夹下的,所以这里我们需要在路径下再添加一个dist/
output: {
path: path. resolve ( _dirname, 'dist' ) ,
filename: 'bundle.js' ,
publicPath: 'dist/'
}
8. ES6语法处理
如果仔细阅读webpack
打包的js
文件,发现写的ES6
语法并没有转成ES5
,那么就意味着可能一些对ES6
还不支持的浏览器没有办法很好的运行我们的代码。
在前面我们说过,如果希望将ES6
的语法转成ES5
,那么就需要使用babel
。
而在webpack
中,我们直接使用babel
对应的loader
就可以了
npm install -- save- dev babel- loader@7 babel- core babel- preset- es2015
{
test: /\.m?js$/ ,
exclude: /{node_modules|bower_components}/ ,
use: {
loader: 'babel-loader' ,
options: {
presets: [ 'es2015' ]
}
}
}
重新打包,查看bundle.js
文件,发现其中的内容变成了ES5
的语法
9. webpack 配置 Vue
1. 引入vue.js
后续项目中,会使用Vuejs
进行开发,而且会以特殊的文件来组织vue
的组件。
所以,下面我们来学习一下如何在我们的webpack
环境中集成Vuejs
现在,我们希望在项目中使用Vuejs
,那么必然需要对其有依赖,所以需要先进行安装
注:因为我们后续是在实际项目中也会使用vue
的,所以并不是开发时依赖
npm install vue -- save
那么,接下来就可以按照我们之前学习的方式来使用Vue
了
import Vue from 'vue'
new Vue ( {
el: '#app' ,
data: {
name: 'xxx'
}
} )
< div id = " app" >
{
{message}}
</ div>
< script src = " ./dist/bundle.js" > </ script>
2. 打包项目 – 错误信息
修改完成后,重新打包,运行程序:
打包过程没有任何错误(因为只是多打包了一个vue
的js
文件而已)
但是运行程序,没有出现想要的效果,而且浏览器中有报错
这个错误说的是我们使用的是runtime-only
版本的Vue
,什么意思呢?
所以我们修改webpack
的配置,添加如下内容即可
resolve: {
alias: {
'vue$' : 'vue/dist/vue.esm.js'
}
}
3. el和template区别
正常运行之后,我们来考虑另外一个问题:
如果我们希望将data
中的数据显示在界面中,就必须是修改index.html
如果我们后面自定义了组件,也必须修改index.html
来使用组件
但是html
模板在之后的开发中,我并不希望手动的来频繁修改,是否可以做到呢?
定义template
属性:
在前面的Vue
实例中,我们定义了el
属性,用于和index.html
中的#app
进行绑定,让Vue
实例之后可以管理它其中的内容
这里,我们可以将div
元素中的{
{message}}
内容删掉,只保留一个基本的id
为div
的元素
但是如果我依然希望在其中显示{
{message}}
的内容,应该怎么处理呢?
我们可以再定义一个template
属性,代码如下:
new Vue ( {
el: '#app' ,
template: '< div id= "app" > {
{
message} } < / div>
data: {
message: 'xxx'
}
} )
重新打包,运行程序,显示一样的结果和HTML
代码结构
那么,el
和template
模板的关系是什么呢?
在我们之前的学习中,我们知道el
用于指定Vue
要管理的DOM
,可以帮助解析其中的指令、事件监听等等
而如果Vue
实例中同时指定了template
,那么template
模板的内容会替换掉挂载的对应el
的模板
这样做有什么好处呢?
这样做之后我们就不需要在以后的开发中再次操作index.html
,只需要在template
中写入对应的标签即可
但是,书写template
模块非常麻烦怎么办呢?
没有关系,稍后我们会将template
模板中的内容进行抽离
会分成三部分书写:template
、script
、style
,结构变得非常清晰
4. Vue组件化开发引入
在学习组件化开发的时候,我说过以后的Vue
开发过程中,我们都会采用组件化开发的思想。
那么,在当前项目中,如果我也想采用组件化的形式进行开发,应该怎么做呢?
查看下面的代码:
我们也可以将下面的代码抽取到一个js
文件中,并且导出
const App = {
template: '<h2>{
{name}}</h2>' ,
data ( ) {
return {
name: '我是APP组件'
}
}
}
new Vue ( {
el: '#app' ,
template: `
<div id="app">
{
{message}}
<App/>
</div>
`
data: {
message: 'xxx'
} ,
components: {
App
}
} )
5. .vue文件封装处理
但是一个组件以一个js
对象的形式进行组织和使用的时候是非常不方便的
一方面编写template
模块非常的麻烦
另外一方面如果有样式的话,我们写在哪里比较合适呢?
现在,我们以一种全新的方式来组织一个vue
的组件
< template>
< h2 class = " title" > {
{name}}</ h2>
</ template>
< script>
export default {
name: "App" ,
data ( ) {
return {
name: '我是.vue的App组件'
} ;
}
}
</ script>
< style scoped >
.title {
color : blue;
}
</ style>
但是,这个时候这个文件可以被正确的加载吗?
必然不可以,这种特殊的文件以及特殊的格式,必须有人帮助我们处理。
谁来处理呢?vue-loader
以及vue-template-compiler
安装vue-loader
和vue-template-compiler
npm install vue- loader vue- template- compiler -- save- dev
修改webpack.config.js
的配置文件:
{
test: /\.vue$/ ,
use: [ 'vue-loader' ]
}
10. plugin 使用
1. 认识plugin
plugin
是什么?
plugin
是插件
的意思,通常是用于对某个现有的架构进行扩展
webpack
中的插件,就是对webpack
现有功能的各种扩展
,比如打包优化
,文件压缩
等等
loader
和plugin
区别
loader
主要用于转换某些类型的模块,它是一个转换器
plugin
是插件
,它是对webpack
本身的扩展,是一个扩展器
plugin
的使用过程:
步骤一:通过npm
安装需要使用的plugins
(某些webpack
已经内置的插件不需要安装)
步骤二:在webpack.config.js
中的plugins
中配置插件
下面,我们就来看看可以通过哪些插件对现有的webpack
打包过程进行扩容,让我们的webpack
变得更加好用
2. 添加版权的Plugin
我们先来使用一个最简单的插件,为打包的文件添加版权声明
该插件名字叫BannerPlugin
,属于webpack
自带的插件。
按照下面的方式来修改webpack.config.js
的文件:
const path = require ( 'path' )
const webpack - require ( 'webpack' )
module. exports = {
...
plugins: [
new webpack. BannerPlugin ( '最终版权归xxx所有' )
]
}
3. 打包html的plugin
目前,我们的index.html
文件是存放在项目的根目录
下的。
在真实发布项目时,发布的是dist
文件夹中的内容,但是dist
文件夹中如果没有index.html
文件,那么打包的js
等文件也就没有意义了。
所以,我们需要将index.html
文件打包到dist
文件夹中,这个时候就可以使用HtmlWebpackPlugin
插件
HtmlWebpackPlugin
插件可以为我们做这些事情:
自动生成一个index.html
文件(可以指定模板来生成)
将打包的js
文件,自动通过script
标签插入到body
中
安装HtmlWebpackPlugin
插件
npm install html- webpack- plugin -- save- dev
使用插件
,修改webpack.config.js
文件中plugins
部分的内容如下:
这里的template
表示根据什么模板来生成index.html
另外,我们需要删除之前在output
中添加的publicPath
属性
否则插入的script
标签中的src
可能会有问题
plugins: [
new webpack. BannerPlugin ( '最终版权归xxx所有' ) ,
new htmlWebpackPlugin ( {
template: 'index.html'
} ) ,
]
4. js压缩的Plugin
在项目发布之前,我们必然需要对js
等文件进行压缩处理
这里,我们就对打包的js
文件进行压缩
我们使用一个第三方的插件uglifyjs-webpack-plugin
,并且版本号指定1.1.1
,和CLI2
保持一致
npm install uglifyjs- webpack- plugin@1.1 .1 -- save- dev
修改webpack.config.js
文件,使用插件:
const path = require ( 'path' )
const webpack = require ( 'webpack' )
const uglifyJsPlugin = require ( 'uglifyjs-webpack-plugin' )
module. exports = {
...
plugins: [
new webpack. BannerPlugin ( '最终版权归xxx所有' )
new uglifyJsPlugin ( )
]
}
查看打包后的bunlde.js
文件,是已经被压缩
过了。
11. 搭建本地服务器
webpack
提供了一个可选的本地开发服务器
,这个本地服务器
基于node.js
搭建,内部使用express框架
,可以实现我们想要的让浏览器自动刷新显示我们修改后的结果。
不过它是一个单独的模块,在webpack
中使用之前需要先安装它
npm install -- save- dev webpack- dev- server@2.9 .1
devserver
也是作为webpack
中的一个选项,选项本身可以设置如下属性:
contentBase
:为哪一个文件夹提供本地服务,默认是根文件夹,我们这里要填写./dist
port
:端口号
inline
:页面实时刷新
historyApiFallback
:在SPA
页面中,依赖HTML5
的history
模式
webpack.config.js
文件配置修改如下:
我们可以再配置另外一个scripts
:
devServer: {
contentBase: './dist' ,
inline: true
}
"dev" : "webpack-dev-server --open"