webpack
1 认识Webpack
1.1 什么是webpack
官方解释:
At its core, webpack is a static module bundlerfor modern JavaScript applications.
从本质上来讲,webpack是一个现代的JavaScript应用的静态模块打包工具。
前端模块化:
- 在前面学习中,我已经用了大量的篇幅解释了为什么前端需要模块化。
- 目前使用前端模块化的一些方案:AMD、CMD、CommonJS、ES6。
- 在ES6之前,我们要想进行模块化开发,就必须借助于其他的工具,让我们可以进行模块化开发。
- 并且在通过模块化开发完成了项目后,还需要处理模块间的各种依赖,并且将其进行整合打包。
- 不仅仅是JavaScript文件,我们的CSS、图片、json文件等等在webpack中都可以被当做模块来使用(在后续我们会看到)。
打包:
- 理解了webpack可以帮助我们进行模块化,并且处理模块间的各种复杂关系后,打包的概念就非常好理解了。
- 就是将webpack中的各种资源模块进行打包合并成一个或多个包(Bundle)。
- 并且在打包的过程中,还可以对资源进行处理,比如压缩图片,将scss转成css,将ES6语法转成ES5语法,将TypeScript转成JavaScript等等操作。
1.2 Webpack和grunt/gulp的对比
-
grunt/gulp的核心是Task
- 我们可以配置一系列的task,并且定义task要处理的事务(例如ES6、ts转化,图片压缩,scss转成css )
- 之后让grunt/gulp来依次执行这些task ,而且让整个流程自动化。
- 所以grunt/gulp也被称为前端自动化任务管理工具。
-
我们来看一个gulp的task
- 下面的task就是将src下面的所有js文件转成ES5的语法。
- 并且最终输出到dist文件夹中。
const gulp = require('gu1p'); const babe1 = require('gu1p-babe1'); gulp.task( 'js ', () => gulp.src('src/* .js ') .pipe(babel({ presets: ['es2015']) })) .pipe(gulp.dest('dist')) );
-
什么时候用grunt/gulp呢?
- 如果你的工程模块依赖非常简单,甚至是没有用到模块化的概念。
- 只需要进行简单的合并、压缩,就使用grunt/gulp即可。
- 但是如果整个项目使用了模块化管理,而且相互依赖非常强,我们就可以使用更加强大的webpack了。
-
所以,grunt/gulp和webpack有什么不同呢?
- grunt/gulp更加强调的是前端流程的自动化,模块化不是它的核心。
- webpack更加强调模块化开发管理,而文件压缩合并、预处理等功能,是他附带的功能。
1.3 webpack依赖环境–node
webpack为了可以正常运行,必须依赖node环境。
怎么安装node来着
查看自己的node版本:node -v
node环境为了可以正常的执行很多代码,必须其中包含各种依赖的包。
安装node的时候,会自动安装软件包管理工具npm(node packages manager)
2 webpack安装
-
全局安装webpack
(这里先指定版本号3.6.0,因为vue cli2依赖该版本,而vue cli3 的配置被隐藏了,不好看)
npm install webpack@3.6.0 -g
(-g 代表全局 – global) -
局部安装webpack(后续才需要)
--save-dev
是开发时依赖,项目打包后不需要继续使用的。cd对应目录 npm install webpack@3.6.0 --save-dev
☆ 为什么全局安装后,还需要局部安装呢?
- 在终端直接执行webpack命令,使用的全局安装的webpack
- 当在package.json中定义的scripts中,包含了webpack命令时,使用的是局部webpack
3 webpack的基本使用
3.1 准备工作:创建文件和文件夹
- 创建文件和文件夹:
- dist文件夹:用于存放之后打包的文件
- src文件夹:用于存放我们写的源文件
- main.js:项目的入口文件。具体内容查看下面详情
- mathUtils.js:定义了一些数学工具函数,可以在其他地方引用,并且使用。具体内容查看下面的详情。
- info.js:根据ES6规范,定义了一些变量,可以在其他地方引用,并且使用。
- index.html:浏览器打开展示的首页html
- package.json:通过npm init生成的,npm包管理的文件(暂时没有用上,后面才会用上)
使用模块化的方式进行开发js文件:
mathutils.js文件中的代码︰
function add(num1, num2) {
return num1 + num2;
}
function mul(num1, num2) {
return num1 * num2;
}
// 1.使用commenjs的模块化规范导出
module.exports = {
add,
mul
}
- info.js文件中的代码︰
// 2.使用ES6的模块化规范导出
export const name = 'why';
export const age = 18;
}
- main.js文件中的代码︰
// 1.使用commenjs的模块化规范导入
const {add, mul} = require('./mathUtils.js')
console.log(add(1, 2));
console.log(mul(1, 2));
// 2.使用ES6的模块化规范导入
import { name, age } from './info.js';
console.log(name, age);
2.2 js文件的打包
-
使用模块化的方式开发的 js文件,它们不可以直接使用。
- 因为如果直接在index.html引入这两个js文件,浏览器并不识别其中的模块化代码。
- 另外,在真实项目中当有许多这样的js文件时,我们一个个引用非常麻烦,并且后期非常不方便对它们进行管理。
-
可以使用webpack工具,对多个js文件进行打包。
- webpack就是一个模块化的打包工具,支持我们代码中写模块化,可以对模块化的代码进行处理。(如何处理的,待会儿在原理中,我会讲解)
- 在处理完所有模块之间的关系后,将多个js打包到一个js文件中,引入时就变得非常方便了。
-
如何打包呢?
- 使用
cd命令
进入dist和src文件夹所在的目录,使用webpack的指令即可:webpack 。./src/main.js ./dist/bundle.js
- 其中,
./src/main
代表入口文件,./dist/bundle.js
设置了出口路径和生成的js文件名称。
- 使用
-
打包成功 的截图如下:
2.3 使用打包后的文件
webpack处理项目直接文件依赖,在dist文件下生成bundle.js文件。
将打包生成的的bundle.js文件在index.html中引入即可。
4 webpack的配置
4.1 webpack.config.js配置
每次使用webpack
的命令都需要写上入口文件和出口作为参数,就非常麻烦。
解决方法:将这两个参数写到配置文件webpack.config.js
(名字固定)中,在运行时,直接读取。
-
手动创建
webpack.config.js
文件// 依赖node包,需要使用npm init建package.json文件帮忙管理node包 const path = require('path'); module.exports = { // 入口:可以是字符串/数组/对象,这里我们入口只有一个,所以写一个字符串即可 entry: './src/main.js', //出口:通常是一个对象,里面至少包含两个重要属性:path和 filename output: { // 注意:path的值要求绝对路径 // 为了动态的获取路径需要用到path包 // path.resolve()方法拼接路径 // __dirname是node自带的全局变量 path: path.resolve(__dirname,'dist'), filename: 'bundle.js' } }
-
终端使用
cd
命令,进入dist和src文件夹所在的目录中,输入webpack
,点击回车即可。
4.2 package.json配置
(package.json文件使用 npm init
命令创建,注意name不能有中文。)
使用局部webpack
因为一个项目往往依赖特定的webpack版本,全局的版本可能和这个项目的webpack版本不一致,导出打包出现问题。 所以通常一个项目,都有自己局部的webpack。
目前,我们使用的webpack是全局的webpack(只要是在终端运行命令的,用的都是全局的),如何使用局部来打包:
- 第一步,项目中需要安装自己局部的webpack
- 这里我们局部安装webpack3.6.0
(注意:webpack 是开发时依赖–只是开发时打包需要,项目打包后不需要继续使用,添加webpack时需要 添加上‘–save-dev’
本地安装webpack后,会新增node_modules文件夹,里面都是默认安装的包。)
(Vue CLI3中已经升级到webpack4,但是它将配置文件隐藏了起来,所以查看起来不是很方便。)
命令如下:
- 这里我们局部安装webpack3.6.0
cd对应目录
npm install webpack@3.6.0 --save-dev
- 第二步,通过node_modules/.bin/webpack启动webpack打包
package.json中定义启动
每次执行都敲这么一长串不方便,我们可以在package.json的scripts中定义自己的执行脚本。
执行我们的build指令
npm run build
定义脚本,添加npm run build
指令到webpack
的映射后,使用该命令,会优先使用本地的webpack版本,而不是全局的webpack版本。
package.json中的scripts的脚本在执行时,会按照一定的顺序寻找命令对应的位置。
- 首先,会寻找本地的node_modules/.bin路径中对应的命令。
- 如果没有找到,会去全局的环境变量中寻找。
5 loader的使用
5.1 什么是loader
- webpack本身可以处理我们写的js代码,自动处理js之间相关的依赖。
- 给webpack扩展对应的loader,就可以在开发中加载css、图片,也包括一些高级的将ES6转成ES5代码,将TypeScript转成ES5代码,将scss、less转成css,将.jsx、.vue文件转成js文件等等这些转化。
5.2 loader的使用
步骤:
- 通过npm安装需要使用的loader
- 在webpack.config.js中的module关键字下进行配置
大部分loader我们都可以在webpack的官网找到,并且学习对应的用法。
5.3 CSS文件处理- 准备工作
项目开发过程中,我们必然需要添加很多的样式,而样式我们往往写到一个单独的文件中。
- 在src目录中,创建—个css文件,其中创建一个normal.css文件。
- 我们也可以重新组织文件的目录结构,将零散的js文件放在一个js文件夹中。
normal.css中的代码非常简单,就是将body设置为red。但是,这个时候normal.css中的样式会生效吗?
口当然不会,因为我们压根就没有引用它。
webpack也不可能找到它,因为我们只有一个入口,webpack会从入口开始查找其他依赖的文件。
在入口文件中引用∶
安装loader:
npm install --save-dev css-loader
npm install --save-dev style-loader
配置loader:
其中:text选项中是正则表达式,意为:以.css结尾的文件。
注:具体的loader如何使用,可以参照:https://webpack.docschina.org/loaders/
6 webpack配置Vue
6.1 引入vue.js
在我们的webpack环境中集成Vue.js的步骤:
-
安装vue包
注:因为我们后续是在实际项目中也会使用vue的,所以并不是开发时依赖,用--save
。
npm install vue --save
-
导入vue包
// 没有写路径时,会先去node_modules文件夹里找 // 源码中导出是用的:export default Vue import Vue from 'vue'
那么,接下来就可以按照我们之前学习的方式来使用Vue了
-
修改vue的版本
修改webpack的配置(webpack.config.js文件),添加如下内容即可
resolve: {
alias: {
// 指向具体的文件,会先去找该文件,是有包括compiler的
// 不设置的话,默认是指向vue.runtime.js的
'vue$': 'vue/dist/vue.esm.js'
}
}
关于第3步的必要性解释:
只做第1、 2步,不修改vue的版本的话,vue版本默认是指向vue.runtime.js的。
重新打包后,运行程序:
打包过程没有任何错误。(因为只是多打包了一个vue的js文件而已)
但是运行程序,没有出现想要的效果,而且浏览器中有报错
![在这里插入图片描述](https://img-blog.csdnimg.cn/8e8439f5c52b44978f8b63fb7bff2cc3.png)
这个错误说的是我们使用的是runtime-only版本的Vue。
runtime-only和runtime-compiler的区别:
1.runtime-only ->代码中,不可以有任何的template(包括Vue实例)
2.runtime-compiler ->代码中,可以有template,因为有compiler可以用于编译template
6.2 el 和template区别(一)
正常运行之后,我们来考虑另外一个问题:
- 如果我们希望将data中的数据显示在界面中,就必须是修改index.html
- 如果我们后面自定义了组件,也必须修改index.html来使用组件
- 但是html模板在之后的开发中,并不希望手动的来频繁修改,是否可以做到呢?
定义template属性:
- 在前面的Vue实例中,我们定义了el属性,用于和index.html中的#app进行绑定,让Vue实例之后可以管理它其中的内容
- 这里,我们可以将div元素中的{{message}}内容删掉,只保留一个基本的id为app的元素
- 但是如果我依然希望在其中显示{{message}}的内容,应该怎么处理呢?
- 我们可以再定义一个
template属性
,代码如下:
new vue({
el: '#app',
template: '<div id="app">{{message}}</div>' ,
data: {
message: 'coderwhy'
}
})
同时有el和template属性的情况下,template的值会将el挂载到的dom替换掉。
好处是:不需要修改html代码。
6.3 el 和template区别(二)
.Vue文件封装处理
但是—个组件以一个js对象的形式进行组织和使用的时候是非常不方便的
一方面编写template模块非常的麻烦
另外一方面如果有样式的话,我们写在哪里比较合适呢?现在,我们以一种全新的方式来组织一个vue的组件
但是,这个时候这个文件可以被正确的加载吗?
必然不可以,这种特殊的文件以及特殊的格式,必须有人帮助我们处理。
谁来处理呢? 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']
}
7 plugin的使用
8 搭建本地服务器
8.1 本地服务器的作用
webpack提供了一个可选的本地开发服务器,这个本地服务器基于node.js搭建,内部使用express框架,可以实现我们想要的让浏览器自动刷新显示我们修改后的结果。
8.2 本地服务器的安装
它是一个单独的模块,在webpack中使用之前需要先安装:
npm install --save-dev webpack-dev-server@2.9.1
注:
- 这个版本需要与webpack的(3.6.1)版本相对应.
- `--dev`代表:开发时依赖,为开发时服务。
- 没有`-g`,属于局部安装。
8.3 本地服务器的配置
配置webpack中的devserver选项
,设置如下属性:
contentBase
:为哪一个文件夹提供本地服务,默认是根文件夹,我们这里要填写./dist
port
:端口号(默认:8080)inline
:页面实时监听刷新historyApiFallback
:在SPA页面中,依赖HTML5的history模式(待补)
webpack.config.js文件配置修改如下:
8.4 本地服务器的启动
不可以在终端直接运行webpack-dev-server
,因为之前是局部安装的,而在终端直接运行的命令,运行的是全局安装的。
可以通过相对路径下的bin目录,找到它来运行。这个方法是可行的,但是视频老师没成功,待补。
更简洁的方式:
在package.json文件中添加配置,再在终端中执行npm run dev
,这时会优先在本地找。
运行成功后,点击该路径,可以打开运行的本地服务。
补充:
- 加上
--open
参数,设置启动本地服务器后,自动打开浏览器,不需要手动点击打开。
- 开发阶段,不进行丑化,否则出错后,在浏览器不好进行调试。
8.5 本地服务启动成功的验证
在main.js文件中删除之前添加的document.writeln('<button>按钮')</button>)
,查看页面显示,该按钮被消除了(不需要再重新编译之类或者手动刷新)。
证明本地服务可以让浏览器自动刷新显示我们修改后的结果。
9 开发时和发布时依赖的配置分离
其实,得先弄清楚,哪些是开发时的依赖配置,哪些是发布时的依赖配置。(这里还不太清楚)
开发时和发布时的公共依赖:
- Vue – 因为代码里有Vue,生产环境肯定也有
开发时依赖:
- webpack – 只是打包工具,生成生产环境的代码。
- loader和plugin – 对源代码进行一些处理,并生成最终代码的预处理器。
发布时依赖:
- webpack的丑化插件
- 代码分离
webpackage.config.js未分离代码:
// 依赖node包,使用npm init建package.json文件帮忙管理node包
const path = require('path');
const webpack = require('webpack')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const UglifyjsWebpackPlugin = require('Uglifyjs-webpack-plugin')
module.exports = {
entry: './src/main.js',
output: {
// path的值要求绝对路径
// 动态的获取路径需要用到path包
// path.resolve()方法拼接路径
// __dirname是node自带的全局变量
path: path.resolve(__dirname,'dist'),
filename: 'bundle.js'
},
module: {
rules: [
{
test: /\.css$/ ,
// 'css-loader 只负责将css文件进行加载
// 'style-loader 负责将样式添加到DOM中
use: ['style-loader','css-loader']
},
{
test: /\.vue$/ ,
use: ['vue-loader']
}
]
},
resolve: {
alias: {
// 指向具体的文件,会先去找该文件,是有包括compiler的
// 不设置的话,默认是指向vue.runtime.js的
'vue$': 'vue/dist/vue.esm.js'
}
}
plugins: [
new webpack.BannerPlugin('最终解释权归XX所有'),
new UglifyjsWebpackPlugin()
]
}
新建一个与dist、src文件夹同目录下的文件夹build,在该文件夹里新建
- base.config.js – 放公共的配置部分–开发时+发布时。
// 依赖node包,使用npm init建package.json文件帮忙管理node包
const path = require('path');
const webpack = require('webpack')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: './src/main.js',
output: {
// path的值要求绝对路径
// 动态的获取路径需要用到path包
// path.resolve()方法拼接路径
// __dirname是node自带的全局变量
path: path.resolve(__dirname,'dist'),
filename: 'bundle.js'
},
module: {
rules: [
{
test: /\.css$/ ,
// 'css-loader 只负责将css文件进行加载
// 'style-loader 负责将样式添加到DOM中
use: ['style-loader','css-loader']
},
{
test: /\.vue$/ ,
use: ['vue-loader']
}
]
},
// base
resolve: {
alias: {
// 指向具体的文件,会先去找该文件,是有包括compiler的
// 不设置的话,默认是指向vue.runtime.js的
'vue$': 'vue/dist/vue.esm.js'
}
}
plugins: [
new webpack.BannerPlugin('最终解释权归XX所有')
]
}
- dev.config.js – 开发时
// 这个是干嘛的来着,就放这一个
module.exports = {
deVServer: {
contentBase: './dist',
inline: true
}
}
- prod.config.js – 发布时
const UglifyjsWebpackPlugin = require('Uglifyjs-webpack-plugin')
module.exports = {
plugins: [
new UglifyjsWebpackPlugin()
]
}
- 安装合并包
npm install webpack-merge --save-dev
- 导入合并包
以dev.config.js 为例,体会合并包的使用
const webpackMerge = require('webpack-merge')
const baseConfig = require('./base.config')
// 合并的使用
module.exports = webpackMerge(baseConfig, {
deVServer: {
contentBase: './dist',
inline: true
}
})