1.webpack官方文档
2.为什么要用webpack
让我们从一个html页面说起,下面的代码可以看到,我在html页面中通过script标签引入了3个JavaScript文件a.js
,b.js
和c.js
,每个文件中分别定义了一个函数并导出(export)给外部用。并且它们之间有一定的依赖关系,c.js
依赖于b.js
,b.js
依赖于a.js
。
// index.html
<!doctype html>
<html>
<head><link href="main.css" rel="stylesheet"></head>
<body>
<div>hello world</div>
<script type="text/javascript" src="a.js"></script>
<script type="text/javascript" src="b.js"></script>
<script type="text/javascript" src="c.js"></script>
</body>
</html>
因为有3个js文件,所以浏览器需要发送三次http 请求来获取这三个文件,然后依次执行其中的代码,如果其中有一个文件因为网络问题而延误了时间,那么整个页面的显示也会被延误。3个文件还好,而当我们的项目逐渐变大,有几十个到上百个JavaScript文件的时候,那问题会更严重,不但有延迟问题,还会遇到很难维护的问题 — 想想如何维护上百个文件的依赖关系?
这时候你会想,是不是我把所有JavaScript文件合成一个文件就好了呢?没错,我们确实可以这样做,这样就减少了http请求数量,让我们的页面加载和显示更快。不过这个合并的阶段是在开发完成之后才进行的,也就是说开发阶段我仍然是有a.js
,b.js
和c.js
等等这些文件的,这样才好开发和维护,因为如果开发阶段就合并的话,就相当于我基于一个可能上万行的文件进行开发,这样的代码是没法维护的。
在开发后完成的这个合并的过程就是打包,这样你就明白为什么要打包了吧。webpack在打包过程中,会分析各个文件之间的依赖关系,然后生成一个依赖图并用文件的形式保存下来,未来浏览器运行代码的时候就可以读取这个文件,就知道了各个代码块之间的关联以及如何调用了。
3.webpack安装
- 安装webpack,运行
npm i webpack -g
全局安装webpack或在项目根目录中运行npm i webpack --save-dev
; - 接着全局安装 webpack-cli(此工具用于在命令行中运行 webpack)
npm i webpack-cli -g
。
4.初步使用webpack打包构建项目
- 运行
npm init
初始化项目 - 创建项目目录
- dist (文件夹是编译后或者压缩后的代码,终发布版本的代码)
- src (文件夹是源码文件)
- css
- img
- js
- index.html (首页)
- index.js (js的入口文件)
- package.json (注:该文件不能写注释,只要是json都不能加,不然运行webpack会报错)
- webpack.config.js
-
安装要其他依赖库,此处以jquery为例,在项目根目录运行
npm i jquery -s
-
在index.js中导入依赖库
// import 用变量接收 from '导入包的名称' // 如果要通过路径的形式,去引入 node_modules 中相关的文件,可以直接省略 路径前面的 node_modules 这一层目录,直接写 包的名称,然后后面跟上具体的文件路径 // 不写 node_modules 这一层目录 ,默认 就会去 node_modules 中查找,如下为引入bootstrap的css样式的方式 //import 'bootstrap/dist/css/bootstrap.css' import $ from 'jquery' $(function(){ $('li:odd').css('backgroundColor','red') })
-
使用webpack对index.js进行打包
原因:浏览器不认识import
这种高级语法,需要用webpack进行处理,将其转换成哼浏览器能识别的语法
(1)创建webpack.config.js
(2)在webpack.config.js中配置打包信息const path = require('path'); module.exports = { entry: './src/index.js', //打包的入口文件 output: { //打包完的输入文件 path: path.resolve(__dirname, 'dist'), //打包输出的文件目录 filename: 'bundle.js' //打包输出的文件名 } };
(3)运行
webpack
初始化项目,会将index.js打包输出到dist中的bundle.js;
(4)在index.js中引入bundle.js<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <script src="../dist/bundle.js"></script> <body> <ul> <li>d</li> <li>d</li> <li>d</li> <li>d</li> <li>d</li> </ul> </body> </html>
5.webpack-dev-server——在内存中自动打包编译bundle.js插件
-
运行
npm i webpack-dev-server -D
然后会报以上错误,提示需要在本地安装webpack,此时已经全局安装过webpack了,也就是说如果想要使用webpack-dev-server这个工具,就必须在本地安装webpacknpm i webpack -D
-
在package.json中有的
scripts
配置脚本命令
由于项目中的 webpack-dev-server是本地安装的,所以无法把它当做脚本命令在终端直接运行(只有那些安装到全局-g的工具,才能在终端上正常运行),所以想要运行我们需要在package.json中有的scripts
配置脚本命令,scripts
是用于指定脚本命令,供npm直接调用。"scripts": { "start": "webpack-dev-server" }
-
运行
npm run start
start
是运行命令名称,在配置中用什么名称,就需要用npm run 名字
来运行,可在配置中更换运行指令的名称。
运行成功,显示如下,可通过ctr并点击http://localhost:8080/进入文件目录
该文件目录中dist并没有生成bundle.js,而是直接托管到了电脑的内存中,所以在根目录中找不到bundle.js,这样做的优点是速度快,且不会消耗我们的磁盘。
由于之前我们引入的是磁盘上的路径,这里应该跟目录的bundle.js,并且访问形式必须是http://localhost:8080/src/,而不能是本地路径<script src="/bundle.js"></script>
-
webpack-dev-server的常用配置
第一种配置方法:在package.json中的scripts
中添加"scripts": { "start": "webpack-dev-server --port 3000 --contentBase src --hot" }
(1)–open 保存自动打开(这个我一般不开,会有174的bug,不要启用即可)
(2)–port 3000 设置端口号
(3)–contentBase src 设置默认打开的根路径
(4)–hot 只更新修改的代码,而不是重新生成bundle.js,还能实现浏览器的无刷新重载,即代码改变了不需要刷新浏览器也能重载。
第二种配置方法:在webpack.config.js的devServer
中配置devServer: { // 这是配置 webpack-dev-server 的第二种形式 port: 3000, // 设置启动时候的运行端口 contentBase: 'src', // 指定托管的根目录 hot: true // 启用热更新 的 第1步 }
6.html-webpack-plugin ——在内存中自动打包编译html页面插件
- 作用:(1)创建一个在内存中生成的html页面的插件(2) 自动把打包好的 bundle.js 追加到页面中去,当使用 html-webpack-plugin 之后,我们不再需要手动处理 bundle.js 的引用路径了,因为这个插件,已经帮我们自动创建了一个合适的 script , 并且,引用了正确的路径
- 步骤:
-
运行
npm i html-webpack-plugin -D
安装插件; -
在webpack.config.js中导入该插件
const htmlWebpackPlugin = require('html-webpack-plugin')
-
在webpack.config.js中的plugins中配置该插件(只要是插件都要在plugins中配置)
配置参数:
template——指定模板页面,将来会根据指定的页面路径,去生成内存中的页面
filename——指定生成的页面的名称plugins: [ // 配置插件的节点 new htmlWebpackPlugin({ // 创建html-webpack-plugin插件 template: path.join(__dirname, './src/index.html'), filename: 'index.html' }) ]
npm run start
-
7.style-loader、css-loader ——打包处理css第三方模块加载器
-
作用:webpack默认只能打包处理 JS 类型的文件(打包import导入的文件),无法处理其它的非 JS 类型的文件;如果要处理非JS类型的文件,我们需要手动安装一些第三方 loader 加载器;
-
webpack 处理第三方文件类型的过程:
- 如果是js文件可以直接打包,如果是非js文件,就会拿到后缀名去webpack.config.js配置文件中的module中的rules内,查找有没有对应的第三方 loader 匹配规则
- 如果能找到对应的规则, 就会调用 对应的 loader 处理 这种文件类型,如果找不到就会报错;
- 在调用loader 的时候,是从后往前调用的,即
{ test: /\.css$/, use: ['style-loader', 'css-loader'] }
是先调用css-loader再调用style-loader; - 当最后的一个 loader 调用完毕,会把 处理的结果,直接交给 webpack 进行 打包合并,最终输出到 bundle.js 中去
-
步骤:
- 运行
npm i style-loader css-loader -D
安装插件; - 在 webpack.config.js 里配置规则
在webpack.config.js里面新增一个 配置节点module, 它是一个对象,用于配置所有第三方模块加载器;module 对象有个 rules 属性,这个 rules 属性是个 数组,该数组中存放了所有第三方文件的匹配和处理规则;module: { // 这个节点,用于配置 所有 第三方模块 加载器,第三方模块就是指用import导入的文件 rules: [ // 所有第三方模块的 匹配规则 { test: /\.css$/, use: ['style-loader', 'css-loader'] }, // 配置处理 .css 文件的第三方loader 规则 { test: /\.less$/, use: ['style-loader', 'css-loader', 'less-loader'] }, //配置处理 .less 文件的第三方 loader 规则 { test: /\.scss$/, use: ['style-loader', 'css-loader', 'sass-loader'] }, // 配置处理 .scss 文件的 第三方 loader 规则 ] }
- 在js入口文件的main.js文件中引入css
import './css/index.css' import './css/index.less' import './css/index.scss'
- 运行
-
配置less、sass的loader案例:
- 运行
npm i less-loader -D
安装插件;
控制面板上会提示“
peerDependencies WARNING less-loader@* requires a peer of less@^2.3.1 || ^3.0.0 but none was installedr”,表示该loader内部依赖于less,因此要再安装less。因为是内部依赖所以不需要在配置文件中定义。 - 运行
npm i less -D
- 在 webpack.config.js 里配置规则
由于less也是样式,因此必须调用’style-loader’和’css-loader’,安装方法如上。
先调用less-loader把他处理成普通样式,然后给css-loader、style-loader调用处理,配置sass-loader同理。module: { rules: [ { test: /\.less$/, use: ['style-loader', 'css-loader', 'less-loader'] }, ] }
- 运行
8.url-loader——处理图片文件地址和字体图标文件地址
- 作用:处理url地址。webpack无法处理css文件中的url地址,不管是图片还是字体图标地址库,只要是url地址都处理不了。因此只要css用到url或需要用到字体图标,都应该引入url-loader
- 步骤:
-
通过css文件在html中加入图片,从入口文件main.js中引入css,运行webpack
html:<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <div class="box" ></div> <div class="box2" ></div> </body> </html>
css:
.box{ width: 220px; height: 120px; background: url('../images/22.jpg'); } .box2{ width: 220px; height: 120px; background: url('../images2/22.jpg'); }
会报错,因为webpack默认无法处理url地址。因此需要借助于url-loader
-
运行
cnpm i url-loader file-loader -D
,url-loader依赖于file-loader。 -
在webpack.config.js中,添加如下module规则:
loader传参的方式和url地址一样,都是?传参,处理方式如下:module: { rules: [ // { test: /\.(jpg|png|gif|bmp|jpeg)$/, use: 'url-loader' }, { test: /\.(jpg|png|gif|bmp|jpeg)$/, use: 'url-loader?limit=7631&name=[hash:8]-[name].[ext]' }, // 处理图片文件的 loader { test: /\.(ttf|eot|svg|woff|woff2)$/, use: 'url-loader' }, // 处理 字体文件的 loader ] }
如果要在
template
中引用src,则要加上如下配置rules: [ { test: /\.(jpg|png|gif|bmp|jpeg)$/, use: [{ loader:'url-loader?limit=3000&name=[name].[ext]', options:{ esModule:false } }] } ]
参数的定义:
(1)limit ——给定的值,是图片的大小,单位是 byte, 如果我们引用的图片,大于或等于给定的 limit值,则不会被转为base64格式的字符串, 如果图片小于给定的 limit 值,则会被转为 base64的字符串。
如果为使用limit参数,或图片小于limit给定值时,图片会被生成base64如下:
如果设置了limit参数,且图片大于等于limit给定值,如果没有设置name参数,name会自动生成一个图片路径地址,该地址名称不是我们实际命名的图片名称,如下:
(2)name=[name].[ext]——如果想要生成的图片名称就是我们实际命名的名称,则要加上该参数,,若没有这句,则会自动生成一个其他名的路径地址。
加上该参数后的生成的图片地址
(3)[hash:8]——因为每张图片的hash值都不一样,所以前面用hash链接,以防webpack帮我们自动生成图片时,不同路径的同名图片发生覆盖的情况,hash值是32位的,这里取前8位
9.bable-loader——处理更高级的ES6语法或者ES7语法
- 作用:
如今 ES6 语法在开发中已经非常普及,甚至也有许多开发人员用上了 ES7 或 ES8 语法。然而,浏览器对这些高级语法的支持性并不是非常好。因此为了让我们的新语法能在浏览器中都能顺利运行,Babel 应运而生。
Babel是一个JavaScript编译器,能够让我们放心的使用新一代JS语法。比如我们的箭头函数、 static语法:
如若没有使用,则会报错。() => console.log('hello babel') class Person { static info = { name: 'zs', age: 20 } }
- 步骤:
-
运行
cnpm i babel-core babel-loader babel-plugin-transform-runtime -D
和cnpm i babel-preset-env babel-preset-stage-0 -D
第一个包相当于转换器,把高级语法转换成低级语法,第二包preset语法相当于字典,提供高级语法和低级语法之间的对应关系,能让webpack知道怎么把高级语法转换成低级语法,二者缺一不可。
注:安装过程中若出现以下错误requires a peer of @babel/core@^7.0.0 but none was installed
,则表示babel-loader的包太高级了,于其他插件不匹配,这时候可以先卸载cnpm un babel-loader
,然后运行cnpm i babel-loader@7 -D
,根据提示下载对应的版本,@后面为版本号。几个解决这个错误。
-
在webpack.config.js中添加配置
module: { rules: [ { test:/\.js$/, use: 'babel-loader', exclude:/node_modules/ } ] }
在配置 babel 的 loader规则的时候,必须 把 node_modules 目录,通过 exclude 选项排除掉,因为如果不排除node_modules,则Babel会把node_modules 中所有的 第三方JS文件,都打包编译,这样,会非常消耗CPU,同时,打包速度非常慢。最终,Babel 把 所有 node_modules 中的JS转换完毕了,但是,项目也无法正常运行!
-
在项目的 根目录中,创建
.babelrc
的Babel 配置文件
(1)这个配置文件属于JSON格式,所以,在写 .babelrc 配置的时候,必须符合JSON语法规范: 不能写注释,字符串必须用双引号。
(2)要配置带有preset和plugin的插件,要在配置项数组里面填写插件的名称,插件名称要去掉preset和plugin前缀。babel-plugin-transform-runtime
、babel-preset-env
、babel-preset-stage-0
.{ "presets": ["env", "stage-0"], "plugins": ["transform-runtime"] }
(3)配置其他Babel插件同理,如我们要配置懒加载插件
运行babel-plugin-syntax-dynamic-import
;
在 .babelrc 文件中加入plugins配置:{ "presets": ["env", "stage-0"], "plugins": ["transform-runtime","syntax-dynamic-import"] }
-
10.在webpack中导入其他包的方式
-
使用npm下载包后,用import引入包名
(1)运行npm i vue -D
下载包;
(2)在入口文件main.js
中导入包import $ from 'vue'
,“from”后面跟着包名,这样就能使用vue包。 -
如何改变import导入包的路径
-
方法一:在node_modules找对应包中的
package.json
,通过改变该文件中的module
属性来改变引入的包文件
需要改变包路径的原因: 默认的包可能是阉割版的,功能不齐全的,如vue,我们尝试的使用“render”,如下
index.html<--index.html--> <body> <div id="app"> </div> </body>
// main.js import Vue from 'vue' var login = { template: '<h1>这是login组件</h1>' } var vm = new Vue({ el: '#app', data: { msg: '123' }, components: { login }, render: function (createElements) { return createElements(login) } })
然后通过webpack运行,结果是报以下错误
这是因为vue默认使用的是vue.runtime.common.js,这是功能不全的版本,这里我们首选我们要回顾一下包的查找规则:(1)找 项目根目录中有没有 node_modules 的文件夹
(2)在 node_modules 中 根据包名,找对应的 vue 文件夹
(3) 在 vue 文件夹中,找 一个叫做 package.json 的包配置文件
(4)在 package.json 文件中,查找 一个 main 属性【main属性指定了这个包在被加载时候的入口文件】因此我们可以找到vue文件中package.json文件,发现他的main指定的路径是
"module": "dist/vue.runtime.common.js"
,只要将该路径修改为"module": "dist/vue.js"
,即可使用完整版的VUE,完整版的文件大小是最大的。 -
方法二:通过
webpack.config.js
配置文件配置导入包的路径resolve: { alias: { // 修改 Vue 被导入时候的包的路径 "vue$": "vue/dist/vue.js" //表示使用require或import时,如果是以vue结尾的,那么去vue/dist/vue.js下面找包 } }
注:这个引入方式的优先级大于第一种引入方式。
-
-
使用npm下载包后,用import直接引入包路径
(1)运行npm i vue -D
下载包;
(2)在入口文件main.js
中导入包import Vue from '../node_modules/vue/dist/vue.js'
,“from”后面跟着包路径,这就是引入的包。
11.vue-loader——解析后缀为.vue文件(vue的组件)的插件
- 步骤
-
运行
cnpm i vue-loader vue-template-compiler -D
,vue-template-compiler
为vue-loader
的内部依赖,也要安装。 -
在
webpack.config.js
文件中配置插件节点和匹配规则const VueLoaderPlugin = require('vue-loader/lib/plugin') //第一步:引入插件 module.exports = { plugins: [ // 第二步:配置插件的节点 new VueLoaderPlugin() ], module: { // 第三步:配置以.vue结尾的匹配规则 rules: [ { test:/\.vue$/, use: 'vue-loader' } ] } };
-
在js入口文件
main,js
中引入以“.vue”结尾的vue文件index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <!-- <script src="/bundle.js"></script> --> </head> <body> <div id="app"> <login></login> </div> </body>
main.js
注意:如果引入的vue包为默认配置的包,即vue.runtime.common.js
时,则不支持以componets的形式引入组件,而应该使用render函数。import Vue from 'vue' import login from './login.vue' var vm = new Vue({ el: '#app', data: { msg: '123' }, // components: { // login // }, render: function (createElements) { return createElements(login) } // render: c => c(login) //以上方式的简写 })
login.vue
<template> <div> <h1>这是登录组件,使用 .vue 文件定义出来的 --- {{msg}}</h1> </div> </template> <script> export default { data() { // 注意:组件中的 data 必须是 function return { msg: "123" }; }, methods: { show() { console.log("调用了 login.vue 中的 show 方法"); } } }; </script> <style> </style>
-
12.vue-ressource
- 作用:实现异步加载 Ajax
- 用法:vue的ajax用法
- 导入步骤
- 运行
cnpm i vue-resource -D
安装包; - 在入口文件中导入
import VueResource from 'vue-resource' Vue.use(VueResource)
- 运行
- 全局设置请求接口的根路径
import VueResource from 'vue-resource'
Vue.use(VueResource)
Vue.http.options.root = 'http://ledong.hmywater.com';//设置请求的根路径
用npm装插件
- 在终端上输出指令生成的默认的package.json:npm init -y
- 在终端上输出指令用npm i来直接安装模块:如安装jquery、bootstrap命令为,npm i jquery -S,npm i bootstrap -S安装的包放在node_modules
扩展:
-D后,安装包会在package中的 devDependencies对象中。简称dev。dev是在开发环境中要用到的。
-S后,安装包会在package中的 dependencies 对象中。简称dep。dep是在生产环境中要用到的。
举个例子:
构建工具:gulp和webpack是用来压缩代码,打包等需要的工具,程序实际运行的时候并不需要,就要放在dev中所以要用 -D
项目插件:例如element ui、echarts这种插件要在运行中使用的,就要放在dep中所以就用 -S
一般我们项目插件,在api中都可以看到,一般都是-S。因为这些插件是在程序运行中使用的。 - 在项目的js入口文件(main.js)中导入模块的方法:import *** from *** 是ES6中导入模块的方式
导入jquery模块:import $ from ‘jquery’,表示从node_modules导入jquery包,用$变量名来接收 - 直接在页面上引用main.js会报错,因为浏览器不认识import这种高级的JS语法,需要使用webpack进行处理,webpack默认会把这种高级的语法转换为低级的浏览器能识别的语法;
在终端输出指令(打包指令):运行webpack 入口文件路径 输出文件路径
如:webpack src/js/main.js dist/bundle.js
把入口文件处理后存储在输入文件路径中
webpack 中如何使用 vue :
- 安装vue的包: cnpm i vue -S
- 由于 在 webpack 中,推荐使用 .vue 这个组件模板文件定义组件,所以,需要安装 能解析这种文件的 loader cnpm i vue-loader vue-template-complier -D
- 在 main.js 中,导入 vue 模块 import Vue from ‘vue’
- 定义一个 .vue 结尾的组件,其中,组件有三部分组成: template script style
- 使用 import login from ‘./login.vue’ 导入这个组件
- 创建 vm 的实例 var vm = new Vue({ el: ‘#app’, render: c => c(login) })
- 在页面中创建一个 id 为 app 的 div 元素,作为我们 vm 实例要控制的区域;