前言
本文将介绍Webpack的使用。
Webpack
介绍
从本质上来讲,webpack是一个现代的JavaScript应用的静态模块打包工具。
目前使用前端模块化的方案有:AMD、CMD、CommonJS、ES6
浏览器可以直接处理的模块化方式只有ES6,如果想使用其他模块化方式,并且需要处理模块间(JavaScript、CSS、图片、json文件等都可以被当作模块)的各种依赖,最后进行整合打包。Webpack可以帮助完成这一系列工作。
Webpack在打包的过程中,会对资源进行处理,比如:压缩图片,将scss转成css,将ES6语法转成ES5语法,将TypeScript转成JavaScript等操作。
Webpack的安装
Webpack依赖于Node环境,因此需要先安装Node.js(这里不做说明)。
开启终端,使用如下命令安装:
代码语言:javascript复制npm install webpack -g
-g
表示全局安装,此外还可以指定版本号:
npm install webpack@3.6.0 -g
局部安装:
代码语言:javascript复制npm install webpack --save-dev
- 在终端直接执行webpack命令,使用的全局安装的webpack
- 当在package.json中定义了scripts时,其中包含了webpack命令,那么使用的是局部webpack
- 通常会指定版本号
前期准备
项目目录结构(如下图):
dist
文件夹:用于存放打包文件src
文件夹:用于存放编写的源文件main.js
:项目的入口文件mathUtils.js
:定义了一些数学工具函数,可以在其他地方引用,并且使用
index.html
:浏览器打开展示的首页htmlpackage.json
:通过npm init
生成,npm包管理的文件
基本使用
下面使用CommonJS模块化方式编写一组代码,并用Webpack打包
首先在mathUtils.js
编写两个数学函数:
//加法
function add(num1, num2) {
return num1 num2
}
//乘法
function mul(num1, num2) {
return num1 * num2
}
//导出
module.exports = {
add,
mul
}
然后在main.js
中导入并使用:
// 使用commonjs的模块化规范
const {add, mul} = require('./mathUtils.js')
console.log(add(20, 30));
console.log(mul(20, 30));
由于使用的是commonjs的模块化规范,因此不能直接放入index.html
中使用,这时就需要Webpack打包。打开终端,进入项目路径,键入如下命令:
webpack .srcmain.js .distbundle.js
命令指明了入口文件和输出的文件名(后面会配置文件,简化打包的命令)。
于是在dist
目录下生成了bundle.js
文件,可以在index.html
中使用:
<!--index.html-->
<script src="./dist/bundle.js"></script>
此外,后续项目中会用到Node的一些包,所以要在终端中键入如下命令:
代码语言:javascript复制npm init
之后会输入一些项目的信息,完成后会在项目路径下生成package.json
文件。
配置
在实际项目中一般不会直接用到webpack
命令,这样会直接找全局的webpack,然而不同的项目可能会用到不同的版本,因此在使用的时候一般会在项目中下载一个本地的webpack(版本号与项目中的一致,否则不同版本有可能会报错)。因此就需要配置一些文件。
webpack.config.js
手动创建webpack.config.js
文件:
利用webpack打包的时候在命令中需要指明入口以及输出文件名,这里会在webpack.config.js
文件中做映射,以简化命令:
const path = require('path')
module.exports = {
entry: './src/main.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
},
}
说明:
const path = require('path')
是利用了node中的函数,用来获取项目路径entry
:设置了项目打包的入口output
:为一个对象类型。path
参数利用path.resolve(__dirname, 'dist')
指明了打包的输出路径(项目的绝对路径(__dirname
)关联自定义路径dist
)。output
规定必须使用绝对路径filename
:指明了打包的输出文件- 由此在终端键入
webpack
命令(无需指定入口文件和输出的文件名)即可进行项目源码的打包
package.json
在终端键入命令npm init
得到package.json
文件:
其中的scripts
参数及为终端命令npm run xxx
的映射。
比如在其中设置"build": "webpack"
关联数据:
"scripts": {
"test": "echo "Error: no test specified" && exit 1",
"build": "webpack"
}
即在终端键入npm run build
就可以执行webpack
命令(会优先寻找本地webpack)。
之后都是使用
npm run xxx
格式的命令。
本地webpack
在实际项目中通常会在项目根路径下下载本地webpack,其版本号应该同项目一致(否则会出错)。
打开终端,进入项目目录,键入如下命令:
代码语言:javascript复制npm install webpack@版本号 --save-dev
然后就会在项目的根目录下生成node_modules
文件夹,里面是webpack的一些包。
在package.json
文件中会自动生成devDependencies
参数,里面是下载的webpack的版本号。
loader的使用
loader是webpack中一个非常核心的概念。
在实际项目开发中不仅有js代码的处理,还需要加载css、图片,也包括一些高级的将ES6转成ES5代码,将TypeScript转成ES5代码,将scss、less转成css,将.jsx、.vue文件转成js文件等等。然而对于webpackp本身的能力来说,对于这些转化是不支持的。
但是可以通过给webpack扩展对应的loader去实现。
loader的使用步骤:
- 通过npm安装需要使用的loader 官网:loaders | webpack 中文网 (webpackjs.com)
- 在
webpack.config.js
中的module
关键字下进行配置
css文件处理
需要同时配置css-loader
和style-loader
假设在项目的根路径下创建一个名为css
的文件夹,里面新建一个名为normal.css
的文件。
要想使用该css样式,首先需要在main.js
(打包的入口文件)中引用:
require('./css/normal.css')
注意: require是Commonjs的引入规范。
接下来需要打开终端,到项目的根路径下,按照官网的命令键入:
代码语言:javascript复制npm install --save-dev css-loader
代码语言:javascript复制npm install style-loader --save-dev
注意: 只下载css-loader仅可以实现打包时对css文件的引用,但是具体的效果展示,还需要下载style-loader 同样的,在下载的时候也可以在指定版本号,格式:xxx-loader@版本号,以2.0.2版本的css-loader为例:
npm install --save-dev css-loader@2.0.2
键入命令,下载完成后,同样地会在package.json的devDependencies关键词下自动生成下载的依赖以及版本号
接下来需要手动在webpack.config.js
中的module
关键字下进行配置,官网会给出详细的配置:
把rules
的部分粘贴到配置文件的相应位置即可。
注意: use列表的读取顺序是自右向左,正常顺序是先加载
css-loader
,后加载style-loader
,这一顺序不可颠倒。
最后就可以通过命令打包css文件了。
less、scss、stylus等文件处理
步骤同上一个css文件处理类似,只不过这里额外说明一点:在官网中的use列表中可以看到loader元素还可以为对象属性,以便于后面添加各种options选项。当然按照上面的方法(直接写xxx-loader
)也是可以的。
图片文件处理
操作
注意:图片文件的处理依然要用到
css-loader
和style-loader
。
前期准备:
如上图,在src文件夹下分别创建css
和img
文件夹,其中css
文件夹中创建一个名为normal.css
的文件,里面是对图片的引用。img文件下有两张图片,其中pic3k.jpg
图片大小为3kb,pic15k.jpg
图片大小为15kb。
normal.css
的代码如下(先引用pic3k.jpg
):
body {
background: url("../img/pic3k.jpg");
}
main.js
引用normal.css
文件: require('./css/normal.css')
根据官网,在终端键入如下命令下载url-loader
(注意版本号):
npm install --save-dev url-loader
在webpack.config.js
文件中手动配置规则
其中:
test
可以配置图片后缀名limit
为图片大小(默认为8kb(8192bit))
然后运行webpack打包,就可以看到效果(如下图)
查看背景图片样式可以看到,图片被自动转化为了base64编码的字符串,在打包文件夹内并未有图片pic3k.jpg
,查看出口文件bundle.js
可以看到该图片的base64编码。
当然上述图片转化的情况仅限于图片文件大小小于设定值(8kb),如果引入较大的图片效果则不一样。
如下代码所示,这次引入的图片大小为15kb(大于设定值大小)
代码语言:javascript复制body {
background: url("../img/pic15k.jpg");
}
再次运行webpack打包,可以看到报错信息:找不到file-loader
- 当加载的图片, 小于设置大小时, 会将图片编译成base64字符串形式
- 当加载的图片, 大于设置大小时, 需要使用
file-loader
模块进行加载
于是我们需要查看官网,在终端键入如下命令引入file-loader
(注意版本号):
npm install --save-dev file-loader
在webpack.config.js
文件中手动配置规则:
注意:这里的配置可以省略。
然后运行webpack打包,执行成功。但是打开index.html
,会发现并没有载入图片,查看后发现报错信息:图片找不到。
实际上,一旦图片大小超出设置格式,就不会转换为base64编码格式,可以在打包输出文件夹看到一个以hash编码命名(避免命名重复)的图片文件,即要引入的图片:
实际上不加以设置,index.html
引用的图片路径为同级目录(项目根目录),但是打包后的图片在输出文件夹dist
中,自然无法找到,因此需要设置访问图片图片的路径。
在webpack.config.js
文件中的output
项添加publicPath
属性,内容为打包输出目录(这里的输出目录为dist
):
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
publicPath: 'dist/'
}
最后再重新打包,成功。
注意: 当然这只是在测试阶段添加的属性。一般在项目移植的时候会将
index.html
文件也移至打包输出文件夹中,这时图片的就同index.html
文件在同一目录下,自然就需要将publicPath
删除。
图片的命名
默认打包后图片命名为32为的hash值(为了避免命名重复),但实际上在图片数量很多的时候我们希望能通过图片文件的命名一眼看出是哪一张,方便测试、维护。此外如果所有的图片都堆到输出文件夹(无子文件夹)会显得很乱,也不方便后期的维护。所以需要对图片进行命名和路径的规范。
在在webpack.config.js
文件中的url-loader
的rules
规范中,在options
中添加name
属性:
{
test: /.(png|jpg|gif|jpeg)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 8192,
name: 'img/[name].[hash:8].[ext]'
}
}
]
}
说明:
img/
:图片文件会存放在输出目录下的img
文件夹中[name].[hash:8].[ext]
:文件的命名规范,表示原文件名.8位hash值.原文件扩展名
ES6语法处理
在之前webpack打包的时候,用到的都是ES6的语法,为了获得更好的兼容性,有时需要ES5的语法(某些浏览器并不支持ES6),因此需要对其进行处理。
在官网下载babel-loader
,由于已经有webpack,因此只需要下载:
-
babel-loader
-
babel-core
-
babel-preset-es2015
说明:官网下载的是babel-preset-env
,它会寻找.babelrc
文件(需要配置),这里暂时只需要es6相关的服务,因此下载babel-preset-es2015
(同样注意指定版本)
npm install --save-dev babel-loader babel-core babel-preset-es2015
在webpack.config.js
文件中添加配置项:
注意presets
中指定的应该为'es2015'
{
test: /.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['es2015']
}
}
}
说明:
exclude
配置项指打包时不包含node_modules
和bower_components
文件夹下的内容。
执行webpack打包,会发现打包文件中均为ES5的语法。
引入Vue.js
基本使用
下面来介绍在webpack环境中集成Vue.js。
只需进入项目根目录,在终端键入如下命令:
代码语言:javascript复制npm install vue --save
说明:
- 因为后续在实际项目中也会使用vue的,所以并不是开发时依赖,因此没有
-dev
- 可以指定版本
然后在index.html
写入Vue实例(模板):
<div id="cpn">
<h2>{{message}}</h2>
</div>
接下来在main.js
文件中引入Vue并创建Vue对象:
import Vue from "vue";
const vue = new Vue ({
el: '#cpn',
data: {
message: 'Hello Webpack'
}
})
说明: 实际上下载的Vue源码中其导出为:
export default Vue;
,因此直接使用import Vue from "vue";
导入语句即可。
最后webpack打包,执行,打包过程并未有误,但是在实际index.html
现实中会出现如下报错信息:
说明:这里涉及到Vue不同版本构建
-
runtime-only
:代码中,不可以有任何的template 在之前index.html
文件里编写的<div>
标签就是Vue实例的template -
runtime-compiler
:代码中,可以有template,因为有compiler可以编译template
现在的错误信息是使用了runtime-only
,但是却想编译template,显然是不可以的。
解决方案:
在webpack.config.js
文件中添加resolve
配置信息:
module.exports = {
entry: './src/main.js',
output: {
...
},
module: {
...
},
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js'
}
}
}
使得每次在使用vue的时候会查找指定的文件 (默认情况下使用的是
vue/dist/vue.runtime.js
)
重新webpack打包,成功显示。
el和template
后期在重新调整显示样式时候,需要重新修改index.html
文件中定义的模板,但是在之后的开发中,并不希望手动频繁的去修改html模板,因此在创建Vue对象时,可以定义template
属性:
import Vue from "vue";
new Vue ({
el: '#cpn',
template: `
<div>
<h2>{{message}}</h2>
</div>
`,
data: {
message: 'Hello Webpack'
}
})
说明:一般在使用的时候可以省略Vue对象的命名,可以直接new。
在前端index.html
代码中直接写<div>
标签即可:
<div id="cpn">
</div>
在vue对象中的el
属性用于绑定相应的标签,在打包的时候,vue会将template
的内容自动替换至前端绑定的标签上。
使用webpack打包后,运行查看:
Vue组件化引入
下面来介绍Vue组件化的引入,会把模板等单独抽离,使得项目结构看起来更为清晰。
首先在index.html
文件中新建一个id为app的<div>
标签,作为Vue实例
<div id="app">
</div>
然后在src
路径下新建一个名为vue
的文件夹,用于存放vue文件
在其中新建一个名为App.vue
的文件:
如果使用WebStorm
编辑器,可以在新建文件中找到Vue类型:
点击创建后会自动生成如下模板:
说明:
-
<template>
:用于书写模板 -
<script>
:用于创建Vue对象,并通过export default
默认导出 -
<style>
:可编写css代码style
标签默认有scoped
属性:该属性是一个布尔属性,如果使用该属性,则样式仅仅应用到 style 元素的父元素及其子元素。
为了更好的体现模块化的思想,我们再创建一个名为Cpn.vue
的文件,为子组件。写入以下内容:
<template>
<div>
<h2 class="cpnTitle">{{title}}</h2>
<p>{{message}}</p>
</div>
</template>
<script>
export default {
name: "Cpn",
data() {
return {
name: "Cpn",
title: "我是cpn子组件的标题",
message: "我是cpn子组件的内容",
}
}
}
</script>
<style scoped>
.cpnTitle {
color: green;
}
</style>
在App.vue
中写入以下内容:
<template>
<div>
<h2 class="appTitle">{{title}}</h2>
<p>{{message}}</p>
<button @click="btnClick">点击弹框</button>
<Cpn/> <!--子组件-->
</div>
</template>
<script>
//导入子组件
import Cpn from './Cpn.vue'
export default {
name: "App",
components: {
Cpn: Cpn //注册子组件
},
data() {
return {
name: 'App',
title: '我是app父组件的标题',
message: '我是app父组件的内容'
}
},
methods: {
btnClick() {
alert('app父组件按钮')
}
}
}
</script>
<style scoped>
.appTitle {
color: brown;
}
</style>
说明: 在
<script>
用import Cpn from './Cpn.vue'
导入子组件,并在components
中进行注册。
在main.js
中写入以下内容:
import Vue from "vue";
import App from './vue/App.vue'
new Vue({
el: '#app',
template: '<App/>',
components: {
App: App
}
})
代码语言:javascript复制说明: 在引入App.vue时可以省略扩展名,只需要在webpack.config.js文件中的resolve关键词中添加extensions配置(.[ext]表示可以在引入时省略的扩展名):
resolve: {
extensions: ['.js', '.css', '.vue'],
alias: {
'vue$': 'vue/dist/vue.esm.js'
}
}
作者: 花猪
链接: https://cnhuazhu.top/butterfly/2021/08/08/Vue/Vue学习-Webpack/
来源: 花猪のBlog
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
template可以直接写标签以引用模板
这时vue组件在打包时还不能够被正确的解析加载,需要下载vue-loader
和vue-template-compiler
:介绍 | Vue Loader (vuejs.org)
在终端键入如下命令(注意版本号):
代码语言:javascript复制npm install vue-loader vue-template-compiler --save-dev
注意:下载后在最后webpack打包的时候可能出现错误。 解决方案:
- 在vue-loader14版本之后开始需要配置额外的插件
- 尝试使用较低的版本(可以尝试使用13的版本)
在webpack.config.js
文件中配置信息:
{
test: /.vue$/,
use: ['vue-loader']
}
最后webpack打包,运行得到展示效果。
Plugin使用
webpack中的插件,就是对webpack现有功能的各种扩展。
使用步骤:
- 通过npm安装需要使用的plugins 某些webpack已经内置的插件不需要安装
- 在
webpack.config.js
中的plugins中配置插件
BannerPlugin
BannerPlugin
属于webpack自带的插件,用于为打包文件做版权声明。
由于是webpack自带的插件,因此在webpack.config.js
文件的首部引入webpack:
const webpack = require('webpack')
然后在module.exports
模块中添加BannerPlugin的配置信息:
module.exports = {
entry: './src/main.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
},
...,
plugins: [
new webpack.BannerPlugin('最终版权归花猪所有')
]
}
注意:plugins为列表。
重新webpack打包,就可以在输出文件首部看到版权信息:
html-webpack-plugin
目前,index.html
文件是直接存放在项目根目录下的,但是项目发布时会发布之前设定的输出文件夹dist
,所以希望该文件下可以自动生成index.html
文件。
首先需要安装该插件,打开终端,进入项目目录,键入如下命令(注意版本号):
代码语言:javascript复制npm install html-webpack-plugin --save-dev
下载完之后可以看到在打包输出文件夹dist
中自动生成了index.html
文件。
但是有些地方我们还需要做一些修改:
首先在项目根目录下的index.html
文件中删去js文件的引入(这个在打包后会自动生成):
然后在webpack.config.js
文件中的output
模块中删除publicPath
信息(如果有的话):
接着在webpack.config.js
文件的首部引入HtmlWebpackPlugin
:
const HtmlWebpackPlugin = require('html-webpack-plugin')
最后在module.exports
模块中添加该插件的配置信息:
module.exports = {
entry: './src/main.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
},
...,
plugins: [
new HtmlWebpackPlugin({
template: 'index.html'
})
]
}
template
:寻找webpack.config.js
文件同级目录下的index.html
文件作为模板。
重新webpack打包,就可以看到打包输出文件夹dist
中的index.html
文件,点击正常运行。
uglifyjs-webpack-plugin
在打包时,对js文件进行压缩处理(删去注释、空格、换行等),以减少文件大小。
首先安装该插件,打开终端进入项目目录,键入如下命令(注意版本号):
代码语言:javascript复制npm install uglifyjs-webpack-plugin --save-dev
接着在webpack.config.js
文件的首部引入UglifyjsWebpackPlugin
:
const UglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin')
然后在module.exports
模块中添加该插件的配置信息:
module.exports = {
entry: './src/main.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
},
...,
plugins: [
new UglifyjsWebpackPlugin()
]
}
重新webpack打包,就可以看到打包输出文件bundle.js
的内容已经被压缩。
注意:在开发阶段不建议使用此插件,因为在调试的时候会很麻烦,到最终发布时可以使用。
搭建本地服务器
之前在调试的时候都需要打包,一旦文件很多的情况下,对于调试来说是非常费时的,所以需要搭建一个本地服务器,类似于热部署,在修改源码之后无需刷新自动更新页面。
webpack提供了一个可选的本地开发服务器,这个本地服务器基于node.js搭建,内部使用express框架。
在使用前需要安装,打开终端,进入项目目录,键入如下命令(注意版本号):
代码语言:javascript复制npm install --save-dev webpack-dev-server
在webpack.config.js
文件的module.exports
模块中添加devServer
配置信息:
module.exports = {
entry: './src/main.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
},
...,
devServer: {
contentBase: './dist',
inline: true
}
}
可选参数:
contentBase
:为哪一个文件夹提供本地服务,默认是根文件夹port
:端口号,默认8080inline
:页面实时刷新historyApiFallback
:在SPA页面中,依赖HTML5的history模式
devServer
配置信息仅在调试时使用,如果是发布版本,应该抹除。
在package.json
中的scripts
模块下添加启动服务命令:
"scripts": {
"test": "echo "Error: no test specified" && exit 1",
"build": "webpack",
"dev": "webpack-dev-server --open"
}
说明:
--open
选项使得在启动服务后自动打开浏览器,也可以不添加。
如此,键入npm run dev
便可启动本地服务器,在改动源码时,浏览器会自动刷新。
配置文件的分离
在之前所有的开发时和发布时依赖的配置都在webpack.config.js
文件中进行,这样会需要不停地修改配置文件,比较繁琐。由此希望将开发时和发布时依赖的配置进行分离。
首先下载插件,打开终端,进入项目目录,键入如下命令(注意版本号):
代码语言:javascript复制npm install webpack-merge --save-dev
接下来就是分离配置文件的操作,首先在项目根目录创建一个build
的文件夹,用于存放配置文件。接着在其中建立如下三个文件:
base.config.js
:公共配置文件部分dev.config.js
:开发时独有的配置文件部分prod.config.js
:发布时独有的配置文件部分
然后将原来的webpack.config.js
配置文件中的内容按如上三个新建立的配置文件的功能进行粘贴。举例:
base.config.js
:
const path = require('path')
const webpack = require('webpack')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: './src/main.js',
output: {
path: path.resolve(__dirname, '../dist'),
filename: 'bundle.js',
// publicPath: 'dist/'
},
module: {
rules: [
{
test: /.css$/,
use: [ 'style-loader', 'css-loader' ]
},
{
test: /.less$/,
use: [{
loader: "style-loader",
}, {
loader: "css-loader",
}, {
loader: "less-loader",
}]
},
{
test: /.(png|jpg|gif|jpeg)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 8192,
name: 'img/[name].[hash:8].[ext]'
},
}
]
},
{
test: /.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['es2015']
}
}
},
{
test: /.vue$/,
use: ['vue-loader']
}
]
},
resolve: {
extensions: ['.js', '.css', '.vue'],
alias: {
'vue$': 'vue/dist/vue.esm.js'
}
},
plugins: [
new webpack.BannerPlugin('最终版权归花猪所有'),
new HtmlWebpackPlugin({
template: 'index.html'
})
]
}
dev.config.js
:
const webpackMerge = require('webpack-merge')
const baseConfig = require('./base.config.js')
module.exports = webpackMerge(baseConfig, {
devServer: {
contentBase: './dist',
inline: true
}
})
prod.config.js
:
const UglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin')
const webpackMerge = require('webpack-merge')
const baseConfig = require('./base.config.js')
module.exports = webpackMerge(baseConfig, {
plugins: [
new UglifyjsWebpackPlugin()
]
})
说明: 在
dev.config.js
和prod.config.js
需要先在首部导入webpackMerge
: const webpackMerge = require('webpack-merge') 以及导入base.config.js
文件: const baseConfig = require('./base.config.js') 然后按照上述格式将基本配置信息(baseConfig)和其他配置信息做结合。
然后就可以删除原webpack.config.js
配置文件了。
注意:
如此,便可在不用修改配置的情况下键入npm run dev
和npm run build
正常进行启动本地服务和打包功能。
后记
掌握了webpack的一些基本原理,对之后学习脚手架会更好理解。