一、介绍
前端模块化
在ES6之前,我们要想进行模块化开发,就必须借助于其他的工具,让我们可以进行模块化开发。
并且在通过模块化开发完成了项目后,还需要处理模块间的各种依赖,并且将其进行整合打包。
而webpack其中一个核心就是让我们可能进行模块化开发,并且会帮助我们处理模块间的依赖关系。
而且不仅仅是JavaScript文件,我们的CSS、图片、json文件等等在webpack中都可以被当做模块来使用(在后续我们会看到)。
这就是webpack中模块化的概念。
打包
就是将webpack中的各种资源模块进行打包合并成一个或多个包(Bundle),并且在打包的过程中,还可以对资源进行处理,比如压缩图片,将scss转成css,将ES6语法转成ES5语法,将TypeScript转成JavaScript等等操作。
二、安装
1、安装webpack首先需要安装Node.js,Node.js自带了软件包管理工具npm,node版本不能小于8.9,不然有些功能用不了。可以看上一节的安装。
2、全局安装webpack(这里我先指定版本号3.6.0,因为vue cli2依赖该版本)
npm install webpack@3.6.0 -g
3、npm init初始化
在node开发中使用npm init会生成一个pakeage.json文件,这个文件主要是用来记录这个项目的详细信息的,它会将我们在项目开发中所要用到的包,以及项目的详细信息等记录在这个项目中。方便在以后的版本迭代和项目移植的时候会更加的方便。也是防止在后期的项目维护中误删除了一个包导致的项目不能够正常运行。使用npm init初始化项目还有一个好处就是在进行项目传递的时候不需要将项目依赖包一起发送给对方,对方在接受到你的项目之后再执行npm install就可以将项目依赖全部下载到项目里。
在根目录下执行,输入项目名和入口文件,入口文件可以随意写一个
npm init
然后会生成一个package.json文件,devDependencies字段表示开发时用到的包
{
"name": "meetwebpack",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
},
"author": "",
"license": "ISC",
"devDependencies": {
"webpack": "^3.6.0"
}
}
4、为什么全局安装后,还需要局部安装呢?
- 在终端直接执行webpack命令,使用的全局安装的webpack
- 当在package.json中定义了scripts时,其中包含了webpack命令,那么使用的是局部webpack
5、全局安装和局部安装参考 NPM install -save 和 -save-dev 傻傻分不清
npm install moduleName # 安装模块到项目目录下
npm install -g moduleName # -g 的意思是将模块安装到全局,具体安装到磁盘哪个位置,要看 npm config prefix 的位置。
npm install -save moduleName # -save 的意思是将模块安装到项目目录下,并在package文件的dependencies节点写入依赖。
npm install -save-dev moduleName # -save-dev 的意思是将模块安装到项目目录下,并在package文件的devDependencies节点写入依赖。
三、基本使用
1、创建一个文件夹结构如下
2、文件和文件夹解析:
dist文件夹:用于存放之后打包的文件
src文件夹:用于存放我们写的源文件
main.js:项目的入口文件。具体内容查看下面详情。
mathUtils.js:定义了一些数学工具函数,可以在其他地方引用,并且使用。具体内容查看下面的详情。
info.js: 定义了几个常量
index.html:浏览器打开展示的首页html
3、mathUtils.js文件中的代码:定义两个函数,然后使用commonjs方式导出
function add(num1,num2) {
return num1+num2
}
function mul(num1,num2) {
return num1*num2
}
//CommonJS 模块化导出
module.exports={
add,
mul
}
4、info.js 使用ES6模块化方式导出常量
//ES6 模块化导出
export const name="wangxiaoyu";
export const age=18;
export const height=123;
5、main.js 导入上面两个文件方法和常量
//commonJS 模块化导入
const math=require("./mathUtils.js")
console.log("Hello Webpack");
console.log(math.add(10,20));
console.log(math.mul(10.20));
//ES6 模块化导入
import {name,age,height} from "./info.js"
console.log(name);
console.log(age);
console.log(height);
6、index.html 代码 导入main.js文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script src="src/main.js"></script>
</body>
</html>
7、直接访问页面会报错,因为浏览器并不识别其中的模块化代码,所以需要使用webpack工具,对多个js文件进行打包。
8、使用webpack命令进行打包
webpack src/main.js dist/bundle.js
9、打包后会在dist文件下,生成一个bundle.js文件,是webpack处理了项目直接文件依赖后生成的一个js文件,我们只需要将这个js文件在index.html中引入即可。将原本引入的src/main.js改为dist/bundle.js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script src="dist/bundle.js"></script>
</body>
</html>
然后查看页面,有正常的输出
四、webpack.config.js配置
如果每次使用webpack的命令都需要写上入口和出口作为参数,就非常麻烦,有没有一种方法可以将这两个参数写到配置中,在运行时,直接读取呢?当然可以,就是创建一个webpack.config.js文件。
1、配置webpack.config.js,需要使用node里的path这个模块
//导入path包 这是从node里导入的,通过npm init初始化时安装
const path=require('path')
module.exports={
//入口: 可以是字符串/数组/对象,这里我们入口只有一个,所以写一个字符串即可
entry:'./src/main.js',
//出口: 通常是一个对象,里面至少包含两个重要属性,path和filename
output:{
//使用node的path模块动态获取路径,通常是一个绝对路径,
path:path.resolve(__dirname,'dist'),
filename:'bundle.js'
},
}
然后可以直接执行webpack命令进行打包,会到当前目录下查找webpack.config.js的配置来打包。
2、局部安装webpack
因为一个项目往往依赖特定的webpack版本,全局的版本可能很这个项目的webpack版本不一致,导出打包出现问题。所以通常一个项目,都有自己局部的webpack。
前面是全局安装的webpack,这样在命令端执行这个命令的时候会使用全局的,这里装一个局部的。
npm install webpack@3.6.0 --save-dev
相关的文件会装在node_modues文件夹中
然后执行node_modules/.bin/webpack
启动webpack打包
3、package.json的scripts中定义自己的执行脚本
执行webpack打包每次执行都敲这么一长串会不方便,可以在package.json的scripts中定义自己的执行脚本。
package.json中的scripts的脚本在执行时,会按照一定的顺序寻找命令对应的位置。首先,会寻找本地的node_modules/.bin路径中对应的命令。如果没有找到,会去全局的环境变量中寻找。
如下,在package.json的scripts字段中配置"build": "webpack"
{
"name": "meetwebpack",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack"
},
"author": "",
"license": "ISC",
"devDependencies": {
"css-loader": "^3.2.0",
"style-loader": "^1.2.0",
"webpack": "^3.6.0"
}
}
然后可以直接执行npm run build
进行打包
五、css-loader
1、介绍
在我们之前的实例中,我们主要是用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关键字下进行配置
2、设置文件
重新组织文件的目录结构,将零散的js文件放在一个js文件夹中。
3、创建一个css文件,其中创建一个normal.css文件。normal.css中的代码非常简单,就是将body设置为red。
body{
background-color: red;
}
4、在入口文件main.js中引用normal.css
//1、commonJS 模块化导入
const math=require("./js/mathUtils.js")
console.log("Hello Webpack");
console.log(math.add(10,20));
console.log(math.mul(10.20));
//2、ES6 模块化导入
import {name,age,height} from "./js/info.js"
console.log(name);
console.log(age);
console.log(height);
//3、依赖css文件
require("./css/normal.css")
5、这个时候如果打包会报错,这个错误告诉我们:加载normal.css文件必须有对应的loader。
6、安装对应的loader
安装较低的版本,因为这个的webpack版本比较低,高版本会不兼容
npm install --save-dev css-loader@3.2.0
npm install --save-dev style-loader@1.2.0
7、然后配置webpack.config.js文件,在module字段中配置相关的loader
在处理css文件过程中,css-loader先加载css文件,再由style-loader来进行进一步的处理
因为webpack在读取使用的loader的过程中,是按照从右向左的顺序读取的,所以会将style-loader放在前面
//导入path包 这是从node里导入的,通过npm init初始化时安装
const path=require('path')
module.exports={
//入口: 可以是字符串/数组/对象,这里我们入口只有一个,所以写一个字符串即可
entry:'./src/main.js',
//出口: 通常是一个对象,里面至少包含两个重要属性,path和filename
output:{
//使用node的path模块动态获取路径,通常是一个绝对路径,
path:path.resolve(__dirname,'dist'),
filename:'bundle.js'
},
module:{
rules:[
{
test: /\.css$/,
//css-loader只负责加载css文件,但是并不负责将css具体样式嵌入到文档中
//style-loader负责将样式添加到DOM元素中
use:['style-loader','css-loader'],
}
]
}
}
8、然后再执行打包,查看页面有应用样式
六、图片文件处理
1、新建一个img文件夹,然后放入图片,img1是小于8k的,img是大于8k的
img1
img3
2、修改normal.css样式文件,将背景改为图片img1
body{
/*background-color: red;*/
background: url(../img/img1.jpg);
}
这个时候如果执行打包会报错,因为没装loader来处理图片文件
3、安装url-loader
npm install --save-dev url-loader
然后配置webpack.config.js,在 module-rulues中新增下面处理规则,匹配到图片则使用url-loader,大小限制为8k
{
test:/\.(png|jpg|gif|jpeg|JPG)$/,
use:[
{
loader:"url-loader",
options:{
limit:8192
}
}
]
}
4、然后执行打包,在查看页面,样式应用成功
npm run build
查看页面,可以看到背景图是通过base64显示出来的,这也是limit属性的作用,当图片小于8kb时,对图片进行base64编码
5、将normal.css样式改为使用img3,大于8kb
body{
/*background-color: red;*/
background: url(../img/img3.jpg);
}
这个时候如果直接打包也会报错,因为大于了url-loader设置的8kb大小,这个时候会使用file-loader来进行打包,所以需要安装file-loader,不需要特殊配置,安装一下就可以了。
6、安装file-loader,这里指定一个低版本,因为使用的webpack版本比较低
npm install --save-dev file-loader@3.0.1
7、然后npm run build重新打包,dist文件夹下会生成一个图片,名称通过哈希生成
如果直接查看页面会显示404
8、webpack会将生成的路径直接返回给使用者,但是,我们整个程序是打包在dist文件夹下的,所以这里我们需要在路径下再添加一个dist/,在webpack.config.js的output中增加publicPath:'dist/'
配置,表示url文件都从dist文件夹中获取
output:{
//使用node的path模块动态获取路径,通常是一个绝对路径,
path:path.resolve(__dirname,'dist'),
filename:'bundle.js',
publicPath:'dist/'
},
然后重新打包再查看页面
9、修改打包后图片文件名称
webpack自动帮助我们生成一个非常长的名字,这是一个32位hash值,目的是防止名字重复,但是,真实开发中,我们可能对打包的图片名字有一定的要求。
比如,将所有的图片放在一个文件夹中,跟上图片原来的名称,同时也要防止重复。所以,我们可以在options中添加上如下选项:
img:文件要打包到的文件夹
name:获取图片原来的名字,放在该位置
hash:8:为了防止图片名称冲突,依然使用hash,但是我们只保留8位
ext:使用图片原来的扩展名
在webpack.config.js的options中新增name: 'img/[name].[hash:8].[ext]'
配置
module:{
rules:[
{
test: /\.css$/,
//css-loader只负责加载css文件,但是并不负责将css具体样式嵌入到文档中
//style-loader负责将样式添加到DOM元素中
use:['style-loader','css-loader'],
},
{
test:/\.(png|jpg|gif|jpeg|JPG)$/,
use:[
{
loader:"url-loader",
options:{
//当加载的图片小于8k,会使用base64方式编译
limit:8192,
//设置打包后的文件夹和名称
name: 'img/[name].[hash:8].[ext]'
}
}
]
}
]
}
然后npm run build重新打包一下,可以看到在dist文件夹下有img文件夹,并且名称为设置的规则,访问页面也正常。
七、ES6语法处理
webpack打包的js文件中ES6语法并没有转成ES5,那么就意味着可能一些对ES6还不支持的浏览器没有办法很好的运行我们的代码。
如果希望将ES6的语法转成ES5,那么就需要使用babel。在webpack中,我们直接使用babel对应的loader就可以了。
npm install --save-dev babel-loader@7 babel-core babel-preset-es2015
然后再webpack.config.js文件中新增rules配置
{
test:/\.m?js$/,
exclude:/(node_modules|bower_components)/,
use:{
loader:'babel-loader',
options:{
presets:['es2015']
}
}
}
查看打包后的文件,里面是var定义变量,则说明转换成功
八、webpack使用vue
1、npm安装vue
npm install vue --save
2、在index.html文件中新增一个id=app的div
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<h2>{{ message }}</h2>
</div>
<script src="dist/bundle.js"></script>
</body>
</html>
3、main.js中新增vue实例,需要先import vue
import Vue from 'vue'
const app=new Vue({
el:"#app",
data:{
message:"Hello Webpack"
}
})
4、然后npm run build重新打包,再查看页面有报错,这个错误说的是我们使用的是runtime-only版本的Vue,runtime-only代码中不能有template。后面再解释runtime-only和runtime-compiler的区别
当前解决这个报错需要在webpack.config.js中配置
resolve:{
//别名,当import vue from vue时,会先到这里来查看是否有指向,如果有则使用配置的vue.esm.js文件。
alias:{
'vue$':'vue/dist/vue.esm.js'
}
}
vue.esm.js在module文件夹下
5、然后重新打包,再查看页面,显示正常
九、Vue组件化开发引入
正常运行之后,我们来考虑另外一个问题:
如果我们希望将data中的数据显示在界面中,就必须是修改index.html
如果我们后面自定义了组件,也必须修改index.html来使用组件
但是html模板在之后的开发中,我并不希望手动的来频繁修改,是否可以做到呢?
1、定义template属性:
在前面的Vue实例中,我们定义了el属性,用于和index.html中的#app进行绑定,让Vue实例之后可以管理它其中的内容
我们可以在index.html将div元素中的内容删掉,只保留一个基本的id为app的div的元素。
<div id="app">
</div>
但是如果我依然希望在其中显示{{message}}等变量的内容,应该怎么处理呢?
我们可以再定义一个template属性,main.js代码如下:
import Vue from 'vue'
const app = new Vue({
el: "#app",
template: '<div><h2>{{ message }}</h2>' +
'<button @click="btnClick">按钮</button>' +
'<h2>{{ name }}</h2></div>',
data: {
message: "Hello Webpack",
name: 'wangxiaoyu'
},
methods: {
btnClick() {
}
}
})
同样能访问,并且template中的内容替换了原本id=app这个div的内容,如果Vue实例中同时指定了template,那么template模板的内容会替换掉挂载的对应el的模板。
2、接着上面的代码,可以将template里的内容统一放到一个对象里,然后在vue实例中使用这个对象注册组件然后在template中使用这个组件。
import Vue from 'vue'
const App={
template: '<div>' +
'<h2>{{ message }}</h2>' +
'<button @click="btnClick">按钮</button>' +
'<h2>{{ name }}</h2>' +
'</div>',
data(){
return{
message: "Hello Webpack",
name: 'wangxiaoyu'
}
},
methods: {
btnClick() {
}
}
}
const app = new Vue({
el: '#app',
template:'<App/>',
components:{
App
}
})
3、再接着下一步,可以将App对象中的代码都放到一个文件中。在src中新建一个名称为vue的文件夹。新建app.js文件,将App对象内容导出
export default {
template: '<div>' +
'<h2>{{ message }}</h2>' +
'<button @click="btnClick">按钮</button>' +
'<h2>{{ name }}</h2>' +
'</div>',
data(){
return{
message: "Hello Webpack",
name: 'wangxiaoyu'
}
},
methods: {
btnClick() {
}
}
}
然后再main.js中导入App对象
import Vue from 'vue'
import App from "./vue/app"
const app = new Vue({
el: '#app',
template:'<App/>',
components:{
App
}
})
这时候打包成功能访问页面。
4、再接着往下,一个组件以一个js对象的形式进行组织和使用的时候是非常不方便的。
(1)所以可以使用vue文件将App对象中的模板和方法进行拆分。在vue文件中再新建一个App.vue文件,这是vue类型的文件,创建出来后默认内容如下。
(2)然后将app.js中的内容分别放到App.vue中,如下,模板内容放在template中,data和methods放在script标签中,style scoped中可以设置class样式,这里设置文字为蓝色
<template>
<div>
<h2 class="title">{{ message }}</h2>
<button @click="btnClick">按钮</button>
<h2>{{ name }}</h2>
</div>
</template>
<script>
export default {
name: "app",
data() {
return {
message: "Hello Webpack",
name: 'wangxiaoyu'
}
},
methods: {
btnClick() {
}
}
}
</script>
<style scoped>
.title{
color: blue;
}
</style>
(3)这种特殊的文件以及特殊的格式,必须有人帮助我们处理。
安装vue-loader以及vue-template-compiler
npm install vue-loader vue-template-compiler --save-dev
这个时候如果打包还会报错,因为vue-loader 14以后的版本之后需要安装corresponding插件,这里要么安装一下插件要么重新安装低版本
ERROR in ./src/vue/App.vue
vue-loader was used without the corresponding plugin. Make sure to include VueLoaderPlugin in your webpack config.
查看package.json我这里安装的是15.9.5的版本
(4)解决方案1:把vue-loader回到v14版本
npm uninstall vue-loader
npm install vue-leader@14.2.2
解决方案2:修改webpack.config.js
新增第2,36,37,38行
(5)然后npm run build 打包后访问页面成功
5、Vue文件再使用子组件
(1)新建一个Cpu.vue文件作为子组件
<template>
<div>
<h2>我是Cpn组件的标题</h2>
<p>我是Cpn组件的内容</p>
<h2>{{ name }}</h2>
</div>
</template>
<script>
export default {
name: "Cpn",
data() {
return {
name: 'cpn组件的name'
}
}
}
</script>
<style scoped>
</style>
(2)在App.vue中导入子组件对象然后注册该子组件并使用
<template>
<div>
<h2 class="title">{{ message }}</h2>
<button @click="btnClick">按钮</button>
<h2>{{ name }}</h2>
<!--使用子组件-->
<Cpn></Cpn>
</div>
</template>
<script>
//导入子组件对象
import Cpn from './Cpn.vue'
export default {
name: "app",
//注册子组件
components:{
Cpn
},
data() {
return {
message: "Hello Webpack",
name: 'wangxiaoyu'
}
},
methods: {
btnClick() {
}
}
}
</script>
<style scoped>
.title{
color: blue;
}
</style>
(3)然后重新打包,再查看,子组件注册并使用成功
十、webpack plugin
1、plugin是什么?
plugin是插件的意思,通常是用于对某个现有的架构进行扩展。
webpack中的插件,就是对webpack现有功能的各种扩展,比如打包优化,文件压缩等等。
2、loader和plugin区别
loader主要用于转换某些类型的模块,它是一个转换器。
plugin是插件,它是对webpack本身的扩展,是一个扩展器。
3、plugin的使用过程:
步骤一:通过npm安装需要使用的plugins(某些webpack已经内置的插件不需要安装)
步骤二:在webpack.config.js中的plugins中配置插件。
4、简单示例、添加版权的Plugin
我们先来使用一个最简单的插件,为打包的文件添加版权声明。该插件名字叫BannerPlugin,属于webpack自带的插件。
按照下面的方式来修改webpack.config.js的文件
const webpack=require('webpack')
plugins:[
new webpack.BannerPlugin('王小雨版权2333')
]
重新打包,查看bundle.js文件的头部,看到如下信息
5、打包html的plugin HtmlWebpackPlugin插件
目前,我们的index.html文件是存放在项目的根目录下的。在真实发布项目时,发布的是dist文件夹中的内容,但是dist文件夹中如果没有index.html文件,那么打包的js等文件也就没有意义了。
所以,我们需要将index.html文件打包到dist文件夹中,这个时候就可以使用HtmlWebpackPlugin插件
HtmlWebpackPlugin插件可以为我们做这些事情:
- 自动生成一个index.html文件(可以指定模板来生成)
- 将打包的js文件,自动通过script标签插入到body中
安装HtmlWebpackPlugin插件,这里指定版本不然会报错
npm install html-webpack-plugin@3.2.0 --save-dev
使用插件,修改webpack.config.js文件中plugins部分的内容如下:
这里的template表示根据什么模板来生成index.html
const HtmlWebpackPlugin=require('html-webpack-plugin')
new HtmlWebpackPlugin({
template:'index.html'
})
另外,我们需要删除之前在output中添加的publicPath属性,因为自动打包生成的index.html文件便会在dist文件夹下了,不需要再额外加publicPath=dist这个配置,否则插入的script标签中的src可能会有问题。
原本index.html中<script src="dist/bundle.js"></script
引入js部分也可以去掉,因为生成的index.html文件会自动引入bundle.js文件。
原本的index.html文件如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
</div>
</body>
</html>
然后执行npm run build打包,然后在dist下会生成index.html文件,自动将bundle.js导入
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
</div>
<script type="text/javascript" src="bundle.js"></script></body>
</html>
可以正常访问
6、js压缩的Plugin
在项目发布之前,我们必然需要对js等文件进行压缩处理
我们使用一个第三方的插件uglifyjs-webpack-plugin,并且版本号指定1.1.1,和CLI2保持一致
npm install uglifyjs-webpack-plugin@1.1.1 --save-dev
修改webpack.config.js文件,使用插件:
const uglifyJsPlugin=require('uglifyjs-webpack-plugin')
new uglifyJsPlugin()
然后执行打包,查看打包后的bundle.js文件已经是压缩过的了,一般平时开发的时候可以注释关闭这个功能方便调试,在最后打包上线的时候再使用。
十一、webpack搭建本地服务器
webpack提供了一个可选的本地开发服务器,这个本地服务器基于node.js搭建,内部使用express框架,可以实现我们想要的让浏览器自动刷新显示我们修改后的结果。
不过它是一个单独的模块,在webpack中使用之前需要先安装它。
npm install --save-dev webpack-dev-server@2.9.1
devserver也是作为webpack中的一个选项,选项本身可以设置如下属性:
contentBase:为哪一个文件夹提供本地服务,默认是根文件夹,我们这里要填写./dist
port:端口号,默认是8080
inline:页面实时刷新
historyApiFallback:在SPA页面中,依赖HTML5的history模式
webpack.config.js文件配置修改如下:
在package.json中配置另外一个scripts:
–open参数表示直接打开浏览器
"dev":"webpack-dev-server --open"
然后执行npm run dev
启动本地服务器,会自动打开浏览器
变化会被记录在内存中,我们可以修改代码进行调试,最后要上线的时候再打包一次,而不用每次修改都打包了。
例如我修改一下变量,然后页面会自动刷新。一般在开发的时候使用这个功能,要上线的时候注释。
十二、webpack配置文件分离
像上面的压缩JS的功能在开发阶段不需要用到,打包上线时才需要用到,而自动刷新功能在开发时需要用到,打包上线时便不需要。对于这种情况,我们可以对webpack的配置文件做下分离。
这里不写了,看晕了已经,直接vue cli开冲!