1. 传统前端开发
传统前端开发一般是 html+javascript+css ,项目目录结构如下:
html文件中定义文档结构,css文件定义样式,JavaScript文件实现对dom的操作。以目前的眼光看,这种开发模式有如下几个弊端:
- JavaScript代码无法实现模块化。对于新的JavaScript规范,如 ES6(ES2015), ES7 由于有些浏览器无法跟上,所以无法使用这些新的特性。
- 各种环境 生产环境,测试环境,开发环境配置维护麻烦。
- 源代码压缩,合并,项目发布困难。
- 无法实现组件化
- 无法实现前后端分离开发。
NodeJS出现后,我们以它为基础,配合生态下的一些工具实现前端的工程化开发成为了可能。实现前端工程化后,其开发模式与传统的模式有很大的不同,可以完美绕开传统开发模式的弊端。
2. NodeJS简介
NodeJS 是一个基于Chrome V8引擎的JavaScript运行环境。它是一个运行时,既不是编程语言,也不是框架。它的npm包管理器可以很方便的管理各种js库。通过它可以使用JavaScript来开发服务端应用程序。
前端开发技术栈都离不开它。
2.1 安装
官方网站 https://nodejs.org/en/download/ 下载对应平台下的安装包。它支持windows, Linux , MacOS. 最好使用LTS版本
安装完毕之后,将其安装目录加入到系统的path环境变量中。
2.2 淘宝npm镜像
NodeJs中的npm 是一个包管理器,这个包管理器在安装js包的时候都是从国外的服务器下载,非常缓慢,有时甚至无法下载。可以使用淘宝的npm镜像,下载会快很多。
打开命令控制台,输入:
npm install -g cnpm --registry=https://registry.npm.taobao.org
执行完这个命令以后,系统会在全局安装一个 cnpm的命令,以后要安装包的时候,可以直接使用cnpm来替代npm命令,它下载JS包的时候就会从淘宝镜像下载。
建议使用cmder命令行工具替代 Windows系统下的控制台工具,cmder 自带git版本控制工具和 Linux Shell。
3. NodeJS的基本使用
使用NodeJS创建一个项目,测试ES6的新语法。新建一个项目文件夹,比如 myNode, 然后打开命令控制台,进入这个目录,然后输入命令:
npm init -y
因为这里只是初始化项目,并没有下载js包,所以无需使用cnpm。
init表示初始化一个项目
-y 表示使用默认配置创建。如果没有 -y,则在初始化的时候,用交互式方式来创建。
创建完毕之后,发现创建了一个 package.json的文件。 这是一个文本文件,其内容是JSON格式。 每一个NodeJs项目都会有一个package.json的文件,这个文件中描述了项目所用到的包,定义了脚本执行的命令,代码如下:
{
"name": "02_02_nodejs",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": ""
}
- name 工程名
- version 版本号
- main: 入口文件。 NodeJs 是JavaScript 引擎,所以项目中编写的都是js脚本文件。
- scripts 定义脚本执行命令
在package.json的同级目录下新建一个 index.js文件
console.log('测试是否支持ES6结构语法')
let node = {
type: "Identifier",
name: "foo"
}
//将node对象中的 type属性赋值给type变量, node对象的name属性赋值给name
let { type, name } = node
console.log(type) // "Identifier"
console.log(name) // "foo"
命令行执行:
node index.js
执行结果:
测试是否支持ES6结构语法
Identifier
foo
- console 是NodeJs的内置对象,用于控制台输出。
- let 是ES6 新语法,定义一个变量,其作用于 var类似,但是与var是有区别的,主要是作用域上的区别。
{
var a = 100
let b = 200
}
console.log(a) //可以正常访问到
console.log(b) //报错,无法访问
可以将运行脚本node index.js
定义到 package.json文件中,然后用npm run xxx来启动
package.json
{
"name": "02_02_nodejs",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": ""
}
现在可以使用命令 npm run start 来执行了。其实 scripts就是为命令行起了一个别名。别名 start比较特殊,执行的时候可以省略 run,直接用 npm start
即可启动。scripts中可以配置多个别名,只要名字不是 start, 启动的时候就必须是 npm run xxx
4. webpack简介
现代前端工程编写的时候,往往会采用新的语言和框架。比如 ES6, TypeScript语言, scss编写样式。用它们编写的代码是无法直接放到浏览器中运行的,必须通过转换以后才能正常运行。
所以需要对前端工程进行构建。构建索要做的事情就是将源代码转换成可以在浏览器中运行的JavaScript,CSS,HTML代码,其中包含了如下内容:
- 代码转换: 如将ES6 代码编译成JavaScript,将SCSS编译成css,将Vue中的(*.vue)文件编译成JavaScript代码等。
- 文件优化:压缩JavaScript,CSS,HTML代码,压缩合并图片等
- 模块合并:将多个模块中的文件合并成一个文件
- 自动刷新:监听本地源代码的变化,自动重新构建,刷新浏览器
Webpack是一款优秀的打包模块化JavaScript的构建工具,在Webpack里,一切文件都被看作是模块。现在很多优秀的前端框架如Vue,React 都是通过它来编译打包构建后发布的。当然我们自己的前端项目也可以使用它来构建发布。
5. webpack入门
新建一个项目,在这个项目中使用JQuery来操作DOM,然后使用webpack进行构建。
5.1 创建项目,添加依赖
项目根文件夹为: 02_03_webpack,打开控制台工具,进入这个目录。输入npm init -y
对项目进行初始化,然后安装jquery库和webpack,代码如下:
npm init -y
cnpm i jquery -S
cnpm i webpack webpack-cli -D
- 因为要安装依赖库,所以使用cnpm从淘宝镜像下载
- i 是 install 的简写,表示安装
- -S 是 --save 的简写
- -D 是 --save-dev的简写, cnpm i webpack webpack-cli -D 表示同时安装 webpack 和webpack-cli(webpack的命令行工具,编译的时候会用到它)
安装完毕之后,来看看package.json文件的内容:(… 表示省略的内容)
{
...,
"dependencies": {
"jquery": "^3.3.1"
},
"devDependencies": {
"webpack": "^4.29.6"
}
}
可以看到, -S
的jquery被放到了 package.json文件的 dependencies节点中, -D的 webpack被放到了 devDependencies 节点中。
现在来说说 dependencies与 devDependencies 的区别:
- devDependencies :在这个节点中声明的依赖包只能在开发的时候有效,项目发布的时候不会用到它。因为webpack只是一个构件工具,NodeJS使用它来构建项目,真正运行的时候并不会用到它。
- dependencies : 这个节点中声明的依赖在开发和发布阶段都有效,会随着发布包一起发布。 jquery是真正在生产环境中运行不可缺少的依赖,所以被放到了这个节点中。
执行完毕之后,项目目录下多了一个 node_modules文件夹,这个文件夹中存放的都是一些第三方的依赖包,这个文件夹只是在开发阶段存在,项目发布后不会用到它。这个文件夹如果被删除,可以在项目根目录下执行 cnpm i
此时NodeJS会根据package.json文件中的描述,自动下载依赖包,重新生成node_modules文件夹。
5.2 新建src/index.js
在项目根目录下新建一个src目录,然后在这个目录下新建一个index.js
src/index.js
//引入jquery对象
const $ = require('jquery')
$(function() {
console.log('Hello Webpack!!!')
$("#app").text('Hello Webpack!!!')
})
5.3 修改package.json
在scripts 节点中添加一个命令行脚本
{
...
"scripts": {
"build": "webpack --mode production"
},
...
}
当在控制台中执行 npm run build
的时候 就会执行后面的命令。它的作用是启动webpack完成编译工作。后面的参数表示 webpack启动的模式为 production
模式。
webpack工作的时候会自动在当前项目的src目录下寻找 index.js文件,找到之后将这个文件作为入口文件进行编译。 index.js文件中有一句 const $ = require('jquery')
表示当前的index.js依赖于 jquery库,此时webpack就会到 node_modules 中寻找 jquery库,将jquery库合并到当前的index.js文件中。
最终webpack执行完毕之后,会在当前项目的根目录下生成一个dist文件夹,这个文件夹中有一个 main.js 文件,这个文件就是src/index.js编译的结果。打开这个文件会发现 这个文件中的内容是被压缩过的,并且合并了jquery库。可以开到main.js文件的大小为87k左右,说明它已经合并了jquery库文件。
注意 src/index.js这个文件是不能直接引入到浏览器中的,因为它不可以直接在浏览器中执行,必须经过转换才可以。
5.4 编写index.html
编写一个html文件,引入编译后的js文件. index.html编写在项目的根目录中:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<script src="./dist/main.js"></script>
<title>Hello Webpack</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
注意这里千万不能引入 src下的index.js文件,引入的是编译以后的结果。
打开浏览器看到如下结果:
6. webpack配置文件
这里使用的是webpack4 这个版本,这个版本在零配置的时候就可以使用。但实际项目中默认的配置是无法满足需要的,此时就需要在配置文件中进行配置。 webpack 默认的配置文件名称为 webpack.config.js
一般将它放在项目的根文件夹下。
webpack.config.js文件的一般格式:
module.exports = {
entry: ...
output: ...
module:{
rules: [
...
]
}
plugins:[
...
]
}
webpack的几个核心基本概念:
- 入口 entry
一个其实的.js文件就是webpack构建的入口。webpack会读取这个文件,并从它开始解析依赖,然后进行打包。零配置下,入口文件默认为 src/index.js
- 输出 output
定义webpack最终构建出来的静态文件生成到什么地方,文件名是什么。
- loader
webpack 中提供一种处理多种文件格式的机制,loader是一个转换器,负责把某种文件格式的内容转换成webpack可以支持打包的模块。比如处理 .css ,.scss,图片文件,字体文件等。 loader都配置在 modlue.rules下。
- plugins
模块代码转换工作由 loader来处理,除此之外的工作都可以交给 plugin来完成。比如文件清理,启动一个本地的web服务等。
6.1 定义输入输出
在项目根文件夹下新建 webpack.config.js 文件,内容如下:
const path = require('path')
module.exports = {
entry: {
index: './src/index.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name]_[hash].js' //name对应entry 中的key, hash为文件的散列值
}
}
补充知识之模块化:
一个模块就是实现特定功能的文件,有了模块,我们就可以更方便地使用别人的代码,想要什么功能,就加载什么模块。模块开发需要遵循一定的规范,否则就都乱套了。一个模块需要暴露出其他地方能够使用的对象和函数,这就是模块的导出, 在需要使用的地方导入模块暴露的功能就能够使用了。
这里主要介绍两种模块化规范
- CommonJS:是一种被广泛使用的JavaScript模块化规范,其核心思想是通过require方法来导入其它模块的依赖,通过module.exports导出需要暴露的接口。Node.js采用了这种方式,而Webpacke是基于Node.js环境工作的,所以Webpack的配置中可以见到使用的是 require和 module.exports来进行导入和导出。通过NPM发布的第三方模块都采用了CommonJS规范,但是这样的代码无法直接运行在浏览器环境下,必须通过转换成标准的ES5才行。
- ES6 模块化: 是国际标准化组织ECMA 提出的JavaScript模块化规范,它在语言层实现了模块化。它使用import进行导入,使用export进行导出。一般我们开发web前端应用的时候,使用ES6的模块化进行开发。
代码解析:
const path = require('path')
const表示定义一个常量, require 是导入模块。 path是NodeJS的内置模块名称,这个模块的用来解析路径。module.exports = {}
导出的是一个对象- entry 定义了webpack打包的入口,即一个名称为 index的入口文件,其路径为 ‘./src/index.js’ , "."表示当前路径,webpack.config.js 文件与package.json文件放在同级目录,也就是项目的根目录, 命令行启动的时候也是在根目录启动的,所以 这个当前路径就是项目的根目录。
- output 定义了webpack的输出,定义了输出的路径 path和输出的文件名 filename
- path.resolve() 是path对象的方法,用于解析路径。第一个参数是父级目录, __dirname(注意前面是两个下划线) 是NodeJs运行环境的变量,是一个常量,表示运行环境的根目录(即命令行执行的时候所在的目录),就是项目的根目录。 最后这个方法执行后的输出路径就是 项目根目录下的dist目录。
- filename指定了输出文件的名称。webpack在输出的时候,会依据
[name]_[hash].js
来决定输出文件的名称, 使用 入口文件的名称 index 来替换中括号中的name, 文件的Hash码(文件的hash码是根据文件的内容计算出来的,只要文件的内容不发生改变,hash码就不会改变,一旦文件内容发生变化,这个hash值就会改变),将文件的hash码作为文件名的一部分的好处在于让浏览器知道是不是最新的文件,因为浏览器为了提高效率会将js文件进行缓存,一旦缓存过js文件后,如果文件名不发生变化,浏览器就会使用缓存中的js文件而不会到服务器中获取最近版本的js,这就造成了js版本已经更新,而浏览器使用的还是旧版本的js文件,这样程序执行就会发生错误。将hash作为js文件名的一部分可以很好解决缓存的问题。
现在在项目根目录下执行 npm run build
,结果如下:
6.2 解决html与js的关联
当我们为js文件名上添加了hash值之后,每次构建js文件的名称都会发生变化,这样就需要手工到html文件中修改js文件的引用。这一点可以为webpack配置一个插件html-webpack-plugin
来解决这个问题。
这个插件需要使用cnpm进行安装,它仅仅是在构建的时候使用,所以使用命令行 :
cnpm i html-webpack-plugin -D
进行安装,安装后,package.json文件内容如下:
{
...
"scripts": {
"build": "webpack --mode production"
},
...
"dependencies": {
"jquery": "^3.3.1"
},
"devDependencies": {
"html-webpack-plugin": "^3.2.0",
"webpack": "^4.29.6",
"webpack-cli": "^3.3.0"
}
}
html-webpack-plugin的配置选项很多,可以参考官方文档: https://www.npmjs.com/package/html-webpack-plugin
现在打开 webpack.config.js文件,使用 html-webpack-plugin 这个插件:
const path = require('path')
//导入html-webpack-plugin 插件,require的时候,会自动到 node_modules中寻找
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: {
index: './src/index.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name]_[hash].js'
},
plugins: [
//实例化插件对象,配置选项可以参考官方文档的options
new HtmlWebpackPlugin({
filename: 'index.html', //生成的HTML的文件名,默认为 index.html
template: './index.html' //以这个文件为模板,重新生成html文件
})
]
}
在 new HtmlWebpackPlugin
的时候,指定了配置选项。其实也可以不用指定任何配置,HtmlWebpackPlugin 会自动创建HTML文件,并将js文件关联到html文件中。
上面的配置中,指定了一个html template(html模板可以是任何扩展名),这个文件中的内容如下:
index.html 模板文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>使用HtmlWebpackPlugin生成新的html文件</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
可以看到,在这个html模板文件中,并没有引入任何js文件。因为这仅仅是一个模板文件,这个模板文件会被HtmlWebpackPlugin 读取,webpack输出的 js文件将会被“插入”到 这个模板中,最终形成一个新的html文件。
现在执行 npm run build
, 可以看到生成的文件如下:
与加入 HtmlWebpackPlugin 文件之前不同的是,多了一个 index.html文件,这个html文件正是HtmlWebpackPlugin 根据模板生成的,其内容如下:
dist/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>使用HtmlWebpackPlugin生成新的html文件</title>
</head>
<body>
<div id="app"></div>
<script type="text/javascript" src="index_e2a817fe93c8a9e3ae74.js"></script></body>
</html>
可以看到, 关联的js被"插入"到了 body的尾部
6.3 自动清理发布文件夹
我们发现,更改了index.js文件之后,每次用 npm run build 进行构建的时候,先前生成的js文件都保留了下来,这些文件已经无用了,此时可以使用 webpack的另外一个插件 clean-webpack-plugin
来做一些清理的工作,官方文档: https://www.npmjs.com/package/clean-webpack-plugin 其实就是每次执行 npm run build 的时候,都删除掉 dist目录
同样这个插件只是开发阶段用到,所以安装的时候添加 -D参数:
cnpm i clean-webpack-plugin -D
package.json文件
{
...
"dependencies": {
"jquery": "^3.3.1"
},
"devDependencies": {
"clean-webpack-plugin": "^2.0.1",
"html-webpack-plugin": "^3.2.0",
"webpack": "^4.29.6",
"webpack-cli": "^3.3.0"
}
}
修改 webpack.config.js文件:
const path = require('path')
//导入html-webpack-plugin 插件,require的时候,会自动到 node_modules中寻找
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CleanWebpackPlugin = require('clean-webpack-plugin')
module.exports = {
entry: {
index: './src/index.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name]_[hash].js' //name对应entry 中的key, hash为文件的散列值
},
plugins: [
new CleanWebpackPlugin(), //删除掉output目录
new HtmlWebpackPlugin({
filename: 'index.html', //生成的HTML的文件名,默认为 index.html
template: './index.html'
}
)
]
}
与 HtmlWebpackPlugin 一样,先导入,然后实例化,配置到 plugins数组中。
这样每次执行 npm run build
的时候,都会将先前生成的dist目录清理掉。 最终项目发布的时候,只需要发布dist目录中的内容即可。
6.4 启动静态服务器
前面配置了 npm run build 这个命令用来发布最终编写好的应用,实际开发的时候总是希望修改代码后,就立即在浏览器中看到执行结果,此时可以为 webpack配置一个 webpack-dev-server
, 它的功能如下:
- 在本机上开启一个端口,提供HTTP服务而不是使用本地文件浏览
- 监听文件的变化并自动刷新网页,做到实时预览
webpack并没有内置 webpack-dev-server, 需要安装,同样安装的时候,使用 -D 参数,官方文档:https://webpack.js.org/configuration/dev-server/
cnpm i webpack-dev-server -D
安装完毕之后,只需要在package.json中配置一个启动脚本命令即可使用了。
package.json
{
...
"scripts": {
"build": "webpack --mode production",
"dev": "webpack-dev-server"
},
...
"dependencies": {
"jquery": "^3.3.1"
},
"devDependencies": {
"clean-webpack-plugin": "^2.0.1",
"html-webpack-plugin": "^3.2.0",
"webpack": "^4.29.6",
"webpack-cli": "^3.3.0",
"webpack-dev-server": "^3.2.1"
}
}
然后在控制台执行命令:
npm run dev #下面是执行命令后输出的内容
> webpack-dev-server
i 「wds」: Project is running at http://localhost:8080/
i 「wds」: webpack output is served from /
i 「wdm」: Hash: 63580e9e4dcdbfd595d7
Version: webpack 4.29.6
Time: 1258ms
Built at: 2019-03-22 19:46:06
Asset Size Chunks Chunk Names
index.html 393 bytes [emitted]
index_63580e9e4dcdbfd595d7.js 651 KiB index [emitted] index
Entrypoint index = index_63580e9e4dcdbfd595d7.js
可以看到,web服务已经启动了,在浏览器中输入http://localhost:8080/ 就可以直接打开页面了。
这里有一个非常疑惑的地方:
web服务器启动的时候都做了什么事情,加载的js文件和html文件在哪里?
注意 web服务器并没有加载 根目录下的 index.html这个文件。因为这个文件只是一个模板,文件中也没有引入任何js文件,只有经过webpack处理之后才会生成可用的html文件。
web服务也没有加载 src中的js文件,因为这个文件是不能直接运行的。
web服务也没有加载 dist文件夹中的内容,可以将dist文件夹删除掉在执行 npm run dev, 服务启动后用浏览器打开依然可以看到代码执行了。
webpack-dev-server 是一个webpack官方提供的小型Express服务器。使用它可以为webpack打包生成的资源文件提供web服务。
Express是一个服务器软件的名称,Tomcat 是 运行Java应用的服务器, IIS是运行 ASP.NET的web服务器, 而Express是运行 NodeJS JavaScript脚本的服务器
webpack-dev-server启动首先会打开默认的端口 8080,如果该端口被占用会打开8081,依次类推。然后启动webpack并加载webpack的配置文件进行打包编译,但是需要注意的是:所有的输出将会输出到 webpack-dev-server所占用的内存中,并不会输出到配置文件中指定的输出目录 。 所以我们在磁盘上根本就看不到webpack的编译输出文件,然后 webpack 将内存中的文件和项目根目录(磁盘上的)一起作为web服务器的根目录。所以当在浏览器上输入 http://localhost:8080/ 或者 http://localhost:8080/index.html 能够打开页面。输入http://localhost:8080/package.json 可以将磁盘上的package.json文件打开。
可以做一个实验,在项目根目录下创建一个 readme.txt文件,文件中随便输入一些内容,然后在浏览器中输入 http://localhost:8080/readme.txt 也能打开这个文件。
6.5 配置静态服务器
可以在package.json文件的脚本中添加一些配置,让静态服务器按照配置来启动,以下是常用的配置项:(参考官方文档说明)
配置项前面用 “–” 修饰, 与值之间用空格分开,见下面的例子
- host 指定一个本机的ip地址,让web服务器绑定这个ip地址,这样局域网中其它机器就可以通过这个ip地址访问这个web服务器了。如果不配置这个选项,默认为localhost 一般开发的时候都是在本机上访问,如果需要其它机器能够访问,可以这样配置:
package.json
{
...
"scripts": {
"build": "webpack --mode production",
"dev": "webpack-dev-server --host 0.0.0.0"
},
...
}
- port 指定端口,默认为8080
package.json
{
...
"scripts": {
"build": "webpack --mode production",
"dev": "webpack-dev-server --port 3000"
},
...
}
- hot : 默认行为是在发现源代码被更新后通过自动刷新整个页面来做到实时预览,开启模块热替换之后,将会在不刷新整个页面的情况下通过用新模块替换老模块来做到实时预览。
- inline: 可以控制浏览器实时刷新
{
...
"scripts": {
"build": "webpack --mode production",
"dev": "webpack-dev-server --port 3000 --hot --inline"
},
...
}
现在运行命令 npm run dev
启动服务, 访问 http://localhost:3000 然后修改 src/index.js中的内容,保存后发现浏览器展现的内容就立即发生了改变。这样就为开发调试提供了极大的方便。
要停止服务,直接在控制台按下 ctrl+c
即可停止服务
- contentBase 配置服务器的文件根目录。默认情况下为当前的执行目录(项目根目录)一般情况下没有必要设置它。前面讲过 DevServer服务器暴露的文件有两类, 本地文件和 内存中webpack构建的结果。再次强调 **webpack构建出来的文件在内存中,我们磁盘上不可见 ** 。contentBase 只能用来配置暴露本地文件的规则,可以通过 --contentBase false 来关闭暴露本地文件
{
...
"scripts": {
"build": "webpack --mode production",
"dev": "webpack-dev-server --port 3000 --hot --inline --contentBase false"
},
...
}
再次用 npm run dev 启动服务,会发现 http://localhost:3000/package.json 已经无法访问了
- progress 与 colors 果项目的编译需要很长的时间。因此,我们可能想要显示某种进度条和颜色.
{
...
"scripts": {
"build": "webpack --mode production",
"dev": "webpack-dev-server --port 3000 --hot --progress --colors --inline --contentBase false"
},
...
}
- open 自动打开浏览器。每次启动服务器,都会自动打开默认的浏览器
{
...
"scripts": {
"build": "webpack --mode production",
"dev": "webpack-dev-server --port 3000 --hot --progress --colors --inline --contentBase false --open"
},
...
}
7.支持ES6语法
如今 ES6 语法在开发中已经非常普及,甚至也有许多开发人员用上了 ES7 或 ES8 语法。然而,浏览器对这些高级语法的支持性并不是非常好。因此为了让我们的新语法能在浏览器中都能顺利运行,就需要进行转换。Babel 这个工具就可以完成这样的转换。
修改 src/index.js 尝试一下ES6的语法。
ES6 中每个语句不必以";" 作为结尾, 如果加上也不会出错。每条语句加不加“;” 完全处于个人习惯
import $ from 'jquery'
let msg = 'webpack'
let getMessage = (name) =>{
return `Hello ${name}`
}
$(()=>{
$("#app").text(getMessage('world'))
})
-
使用import … from 导入模块
-
let 定义变量,与var类似,但let定义的变量只在块级作用域中有效
-
getMessage是一个函数,后面定义的函数使用了ES6的箭头函数。其实箭头函数就是一个匿名函数,箭头函数与传统的用 function 定义的函数还有有一些差别的,主要是函数体内的 this指向不同。 详细区别请参考 http://es6.ruanyifeng.com/
retrun 后面返回的是一个字符串。但这个字符串使用的既不是单引号,也不是双引号,而是反引号(键盘数字键1 左边的键),它是ES6中的字符串模板,可以将变量 使用 ${ 变量 } 的方式 “插入”到字符串模板中,当然也可以使用 “+” 进行字符串链接,但这种字符串模板的写法更加方便
这里不对ES6语法做过多的描述,后面的学习中用到了会做详细解释。可以通过 http://es6.ruanyifeng.com/ 系统的学习ES6 语法。
现在执行 npm run dev
启动服务后,发现Chrome浏览器能够正常执行,这是因为 Chrome 浏览器已经开始支持ES6 的语法了(目前依然没有完全支持),但是IE系列的浏览器就没有那么幸运了,比如在 IE11 浏览器中(也可以在 360等浏览器中调整到兼容模式下选择兼容的IE版本)直接报出了语法错误
此时就需要使用 Babel 工具做转换。
7.1 安装Babel
因为Babel只是辅助工具,所以安装的时候加入 -D 参数, 这里安装的是 Babel 7 版本
cnpm i -D babel-loader @babel/core @babel/preset-env
package.json
{
...
"dependencies": {
"jquery": "^3.3.1"
},
"devDependencies": {
"@babel/core": "^7.4.0",
"@babel/preset-env": "^7.4.2",
"babel-loader": "^8.0.5",
"clean-webpack-plugin": "^2.0.1",
"html-webpack-plugin": "^3.2.0",
"webpack": "^4.29.6",
"webpack-cli": "^3.3.0",
"webpack-dev-server": "^3.2.1"
}
}
7.2 配置webpack loader
因为webpack编译打包的时候是不知道 Babel存在的,而转换的工作是有Babel完成的,这就需要让webpack 将打包的js文件交给 Babel 完成转换。
在webpack中对于文件的处理,是由 loader来完成的,前面安装Babel的时候,就安装了一个 babel-loader ,将它配置到webpack中就可以做转换了。
webpack.config.js
const path = require('path')
//导入html-webpack-plugin 插件,require的时候,会自动到 node_modules中寻找
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CleanWebpackPlugin = require('clean-webpack-plugin')
module.exports = {
entry: {
index: './src/index.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name]_[hash].js' //name对应entry 中的key, hash为文件的散列值
},
module: {
rules: [{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/
}]
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
filename: 'index.html', //生成的HTML的文件名,默认为 index.html
template: './index.html'
})
]
}
webpack配置loader是配置到 module.rules中的,rules是个复数,表示可以定义很多规则。
- test 是一个正则表达式,匹配的是 以 “.js” 结尾的文件,也就是JavaScript文件。当webpack打包遇到js文件的时候就会启动 babel-loader 这个loader来做转换工作
- loader 指定loader的名称
- exclude : 是一个正则表达式。 表示排除那些文件。 因为 项目找那个的 node_modules这个文件夹是安装的库文件,我们只需要转换本项目中的js文件,所以需要将 node_modules文件夹以及子文件夹下的所有js文件都排除掉。
7.3 Babel的配置文件
虽然在webpack中配置了loader,但是Babel此时还不能发挥它的作用,需要在项目根目录下创建一个 .babelrc 文件(文件名前面有一个 .),内容如下:
.babelrc
{
"presets": [
"@babel/preset-env"
]
}
presets属性告诉Babel要转换的源码使用了哪些新的语法特性. @babel/preset-env 自动将ES6版本以及以上的代码转换为es5
现在项目的目录结构如下:
现在使用命令 npm run dev
启动,发现IE浏览器已经可以正常显示了
8. 关于处理CSS
目前还没有介绍webapck如何处理CSS,关于这部分内容后面用到了再介绍。实际上 关于webpack的配置内容非常多,它毕竟只是一个工具,后面我们实际需要用webapck做一些处理的时候再去做相关的配置。比如 css的处理,转换 scss,抽取css成独立的文件,引入第三方CSS库等内容。
9. 开发Vue应用
下面从新建项目开始,一步一步来搭建开发Vue应用的环境。官方提供了初始化vue应用的命令行工具,这里先不使用这个命令行工具,因为用这个工具生成的项目配置非常复杂,等我们理清了Vue项目的相关知识后,再采用Vue的 CLI工具来创建应用。
9.1 初始化项目
新建一个文件夹 02_09_vue_prj, 使用命令行进入这个目录,然后输入 npm init -y
也可以新建完目录后,在这个目录下用VSCode打开:
在VSCode工具中打开终端,在这里输入命令行
9.2 安装依赖包
vue 最终发布的时候要使用,所以使用 -S, 其它都是在开发环境下使用所以统一使用 -D
依赖包安装后最终都写入到了 package.json文件中了,可以重复使用的。可以直接拷贝生成好的 package.json文件,然后直接在命令行中使用 cnpm i
安装即可。
cnpm i vue -S
cnpm i webpack webpack-cli html-webpack-plugin clean-webpack-plugin webpack-dev-server babel-loader @babel/core @babel/preset-env -D
9.3 pacakge.json文件
{
"name": "02_09_vue_prj",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "webpack --mode production",
"dev": "webpack-dev-server --port 3000 --hot --progress --colors --inline --contentBase false --open"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"vue": "^2.6.10"
},
"devDependencies": {
"@babel/core": "^7.4.0",
"@babel/preset-env": "^7.4.2",
"babel-loader": "^8.0.5",
"clean-webpack-plugin": "^2.0.1",
"html-webpack-plugin": "^3.2.0",
"webpack": "^4.29.6",
"webpack-cli": "^3.3.0",
"webpack-dev-server": "^3.2.1"
}
}
9.4 项目结构规划
- 根目录下存放一个 index.tpl ,它是一个HTML模板文件,webpack 根据模板文件生成index.html 这里与前面不同的是把扩展名改成了tpl加以区别。
- 所有的JavaScript文件存放到 项目根目录/src目录中,入口文件为 main.js (前面使用的是index.js)
index.tpl
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Vue入门</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
HTML文件中只有一个 id为app的div标签,再无其它内容。这意味着应用所有的内容都有vue来生成,Vue中包含了视图(页面呈现),数据模型(展现的数据)。可以用来做SPA(single page web application) 应用,即单页面应用,也就是只有一个HTML页面。
main.js
//import Vue from 'vue'
import Vue from 'vue/dist/vue.js' //导入vue依赖
new Vue({
el: '#app',
template: '<div> <h1> {{ message }} </h1> </div>',
data: {
message : '这是一个Vue SPA应用'
}
})
第一行和第二行都是导入Vue依赖,但现在我们要使用第二行的导入。官方文档中有这样一段描述:
详情请看官方文档: https://cn.vuejs.org/v2/guide/installation.html#%E5%AF%B9%E4%B8%8D%E5%90%8C%E6%9E%84%E5%BB%BA%E7%89%88%E6%9C%AC%E7%9A%84%E8%A7%A3%E9%87%8A
9.5 webpack.config.js文件
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CleanWebpackPlugin = require('clean-webpack-plugin')
module.exports = {
entry: {
index: './src/main.js' //入口文件更改为main.js
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name]_[hash].js'
},
module: {
rules: [{ //支持ES6 语法 转换
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/
}]
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
filename: 'index.html',
template: './index.tpl' //模板文件更改为 index.tpl
})
]
}
9.6 Babel配置文件
.babelrc
{
"presets": [
"@babel/preset-env"
]
}
9.7 运行
npm run dev
可以看到 HTML模板中的
9.10 Vue的template
可以看到vue的template就是最终要在页面上呈现的内容,包含有HTML代码。上面的代码将HTML代码作为字符串写入到了js文件中,这种做法非常不可取,那 Vue的template应该写到什么地方呢?
第一种做法是写到HTML中,在HTML文件中使用 标签或者
template 标签 在chrome浏览器中不会渲染出来,但是IE浏览器会将其中的内容显示出来
使用 script 标签,Chrome和IE都不会显示其中的内容。所以为了更加兼容,应该使用script标签
修改 index.tpl
<body>
<div id="app"></div>
<script type="x-template" id="Greet">
<div> <h1> {{ message }} </h1> </div>
</script>
</body>
修改 src/main.js
//import Vue from 'vue'
import Vue from 'vue/dist/vue.js'
new Vue({
el: '#app',
template: '#Greet',
data: {
message : '这是一个Vue SPA应用, template在 index.html文件中'
}
})
执行结果:
可以看到模板会一直存在于 HTML文档中。
这种在HTML中编写vue模板的做法在模板比较多的时候,就是一场灾难。 Vue 强大的功能之一就是组件化。Vue组件具有封装可复用的特点,能够让你在复杂的应用中拆分成独立模块使用。
注意,所有的 Vue 组件同时也都是 Vue 的实例
9.11 初步认识Vue组件(Component)
Vue组件可以将视图,数据和逻辑处理进行很好的封装,封装后可以多次复用。这样就可以将Vue模板封装到组件中。创建了一个组件就相当于创建了一个自定义的HTML标签
修改 src/main.js ,在这个文件中创建一个Vue组件,组件的名字叫做 App, 其实相当于创建了一个自定义的标签
import Vue from 'vue/dist/vue.js'
// Vue提供了一个静态方法 component, 可以创建一个组件
// 第一个参数是组件的名称
// 第二个参数是一个对象,这个对象与实例化Vue对象的时候配置参数是一致的。
Vue.component("App", {
template: `
<div> <h1> {{ message }} </h1> </div>
`,
data: function(){
return {
message : '这是一个Vue SPA应用, 创建名称为App的组件'
}
}
})
new Vue({
el: '#app',
template: '<App />',
})
上面的代码中,使用了 Vue.componnet 方法创建了一个全局组件,相当于创建了一个 标签,所谓的全局组件就是在任何地方都可以使用 。下面new Vue对象的时候,就指定了template,使用了 标签
需要注意的是创建第二个参数,与new Vue的时候传递的参数基本是一致的, 但 data部分是不一样的
组件中的data必须是一个函数
new Vue的时候 data 是一个对象。为什么组件中的data必须是一个function呢?
因为组件一旦创建了是可以反复使用的,把组件看成是一个类的话,就表示可以多次的实例化。如果组件中的data 是一个对象,根据JavaScript的原型链原理,所有的实例对象都会共享这个data, 一旦有一个组件对象修改了data中的数据,也就意味着其它组件实例对象中的data也会跟着改变,因为所有的实例对象共享了同一个data对象。 所以在定义Vue组件的时候,把data设置成一个函数,这个函数return的时候返回一个对象,这样就可以避开这个问题了。
index.tpl 文件中不再写template
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Vue入门</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
运行后的结果:
问题: 组件中有template, 还是把html代码写到了JS中,如果不写到JS中,依然要写到 html文件中,前面的问题似乎依然没有解决?
其实Vue应用可以将组件写到一个单独的文件中,为了更清晰的表达文件的内容,这个文件的扩展名可以写成 .vue (当然不一定非得是vue的扩展名), Vue官方提供了 webpack 的 loader来解析这个vue文件,然后将它编译为js代码。
9.12 组件代码放入单独的.vue文件中
在src文件夹中新建一个components 文件夹,专门用来存放 扩展名为 .vue的组件文件,在这个文件夹中新建App.vue文件
src/components/App.vue
<template>
<div>
<h1> {{ message }} </h1>
</div>
</template>
<script>
export default {
data() {
return {
message : ' 创建名称为App的组件,组件放在App.vue文件中'
}
}
}
</script>
<style scoped>
/* 这里写css样式 */
</style>
可以看到,这个 .vue文件中包含了三部分内容:
- template: 这就是这个组件的模板部分,在中间写html代码和 以及 vue模板表达式
- script: JS代码部分。 export default 是ES6 的默认导出语法。详细语法参看 http://es6.ruanyifeng.com/#docs/object。可以看到导出的就是一个 对象。但是我们发现 这个data本应该是一个function,这里看到的ES6 函数的扩展写法,详细语法参看 http://es6.ruanyifeng.com/#docs/function 。 当然也可以写成:
export default {
data: function(){
return {
message: ' 创建名称为App的组件,组件放在App.vue文件中'
}
}
}
- style: 编写样式, style后面的 scoped表示在这里写的样式只在当前文件内有效
可以将一个vue文件就是一个ES6 模块,最终暴露出去的是一个组件对象。
在src/main.js中使用组件
import Vue from 'vue/dist/vue.js'
import App from './components/App.vue' //导入组件App 指向了组件对象
Vue.component("App",App) //组件必须注册后才能使用,这里采用的是全局注册。
new Vue({
el: '#app',
//使用组件App组件中的内容将会替代el指向的HTML标签 <div id='app'> </div>
template: '<App />',
})
VSCode默认不会识别 .vue文件,需要额外安装VSCode的插件。
index.tpl 不用做任何改动
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Vue入门</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
现在就执行 npm run dev 的话,是不能够运行的。因为 webpack根本就不知道如何解析 vue文件,所以必须要为 webapck配置 能够解析 .vue 的loader才行
9.13 配置 Vue Loader
Vue官方专门编写了 Vue的 webpack Loader和插件,其官方文档为 https://vue-loader.vuejs.org/zh/
首先需要安装loader, 因为同样是辅助工具所以安装的时候使用 -D 选项
cnpm i vue-loader vue-template-compiler css-loader -D
因为 vue-loader依赖于 css-loader,它是解析css内容的。 所以要一起安装
package.json
{
...
"dependencies": {
"vue": "^2.6.10"
},
"devDependencies": {
"@babel/core": "^7.4.0",
"@babel/preset-env": "^7.4.2",
"babel-loader": "^8.0.5",
"clean-webpack-plugin": "^2.0.1",
"css-loader": "^2.1.1",
"html-webpack-plugin": "^3.2.0",
"vue-loader": "^15.7.0",
"vue-template-compiler": "^2.6.10",
"webpack": "^4.29.6",
"webpack-cli": "^3.3.0",
"webpack-dev-server": "^3.2.1"
}
}
修改 webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CleanWebpackPlugin = require('clean-webpack-plugin')
// 导入 VueLoader插件
const VueLoaderPlugin = require('vue-loader/lib/plugin')
module.exports = {
entry: {
index: './src/main.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name]_[hash].js'
},
module: {
rules: [{ //支持ES6 语法 转换
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/
}, {
test: /\.vue$/, //解析vue文件
loader: 'vue-loader'
}, {
test: /\.css$/, //解析css文件,目前还没有用到,先写上
use: [
'vue-style-loader',
'css-loader'
]
}]
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
filename: 'index.html',
template: './index.tpl'
}),
new VueLoaderPlugin() // VueLoader插件
]
}
现在执行npm run dev ,看到执行结果: