**Node.js **是一个 JavaScript 运行环境,不是一个 js 文件,它是基于Chrome JavaScript运行时建立的平台,用于方便地搭建响应速度快、易于扩展的网络应用;它的实质是对Chrome V8引擎进行了封装,它提供了替代的API使得V8在非浏览器环境下运行的更好。V8引擎执行JavaScript的速度非常快,性能非常好。Node.js是一个让JavaScript运行在服务端的开发平台,它让JavaScript成为与PHP、Python等服务端语言平起平坐的脚本语言。Chrome浏览器和Node.js在解析JavaScript都是用了V8引擎。
npm
当我们开发一个网站依赖的js代码越来越多,比如说你要下载jQuery、bootstrap等等各种三方的包,但是下载各种的包,下载源码解压再使用,真的很麻烦,于是就出现了包管理器:npm,npm(Node Package Manager)就是 Node 包管理工具 ,和maven、gradle类似,不过maven和gradle是管理Java的jar包的,而npm是用来管理js的包的。其实现的思路是一样的,都是有一个远程代码仓库(registry),在远端仓库里上传所有需要被共享的js代码,每个js文件都有自己唯一标识;用户想使用某一js的时候,只需要引用对应的唯一标识,js文件就会自动下载下来。我们需要使用时直接通过npm安装就可以了,不用管哪个源码在哪里,如果我们需要使用模块A,而模块A又依赖于模块B,B依赖于其他的模块,此时npm会根据依赖关系把所有依赖的包都下载下来且管理起来,为我们省去了n多的麻烦。
因为npm的包是从国外网站下载的,为了提升npm的下载体验,因此需要使用NRM工具,nrm工具只提供了几个常用jar包的地址,它仅仅是提供下载包的URL地址,我们可以在这些地址中进行切换,装包的工具都是使用的npm。nrm中包含了很多的地址比如: npm官网地址:registry.npmjs.org; taobao镜像;cnpm镜像等等。
运行 npm i nrm -g 全局安装 nrm 包
使用 nrm ls 查看当前所有可用的镜像源地址以及当前使用的镜像源地址;
使用 nrm use npm 或 nrm use taobao 切换不同的镜像地址;
网页中静态资源过多带来的问题:
1.网页加载速度慢,因为要发起很多的二次请求加载静态资源;
2.依赖关系复杂
解决上述问题的思路:
图片:合并、压缩、精灵图、base64编码格式(适用于小图片)
使用webpack可以帮我们解决优化包资源的依赖关系。webpack是基于整个项目构建的;
使用gulp,基于task任务(或者说是功能点) - 适用于一些小型的项目,可以通过多个任务(或者说是功能点)来完成一个项目的构建;
webpack 是基于Node.js开发出来的一个前端的项目构建工具,也就是说必须要先安装Node才能运行webpack。它是一个前端自动化构建工具,可以完美实现资源的合并、打包、压缩、混淆等诸多功能。
运行 npm i webpack -g 全局安装webpack,这样就能在全局使用webpack的命令
在项目目录中运行 npm i webpack --dave-dev 安装到项目依赖中
如何安装jQuery:
npm init -y
npm i jquery -S
这个配置文件其实就是一个js文件,通过Node中的模块操作,向外暴露了一个配置对象,该配置对象包括entry属性(入口)指定要用webpack打包的文件(比如main.js);output 属性 配置输出文件
const path = require('path')
module.exports = {
//通过Node中的模块操作,向外暴露了一个配置对象
entry: path.join(__dirname,'./src/main.js'),//入口 即要使用webpack打包的那个文件
output: { //输出文件的相关配置
path: path.join(__dirname,'./dist'),//输出的路径
filename: 'bundle.js'//指定输出文件的名称
},
mode: 'development'//这个必须要再package.json文件中 声明,见下文
}
{
"name": "webpack-demo",
"version": "1.0.0",
"description": "",
"main": "main.js",
"dependencies": {
"jquery": "^3.5.1"
},
"devDependencies": {},
"scripts": {//这个节点中可以配置一些命令
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "webpack --mode development",
"build": "webpack --mode production"
},
"keywords": [],
"author": "",
"license": "ISC"
}
上述webpack.config.js和package.json两个文件后,可以输入webpack然后回车。
webpack 做了哪些事情呢?
但是上述的每次编辑代码后都要手动输入webpack命令进行打包才能看到最新的数据更新,很不友好,因此出现了自动打包编译工具:webpack-dev-server
使用步骤:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sIrpmhsS-1593848774387)(/Users/zq/Library/Application Support/typora-user-images/image-20200612105513970.png)]
因为我们在packge.json文件中配置了scripts的dev命令为
"scripts":{
"dev":"webpack-dev-server"
}
只需要输入命令 npm run dev 就可以运行该脚本了
执行后会发现如下输出:
ℹ 「wds」: Project is running at http://localhost:8080/
ℹ 「wds」: webpack output is served from /
ℹ 「wds」: Content not from webpack is served from /Users/zq/Desktop/arvinwork/vsforstudy/2020June/test612
ℹ 「wdm」: Hash: ee7bd2c356849dccdd6b
我们可以看出,
第一行:项目其实以服务的形式启动了起来,位于‘http://localhost:8080/’
第二行“webpack output is served from /” 也就是说webpack命令输出的文件保存在/ 路径下,即输出文件托管到了项目的根目录先。也就是说我们设置的webpack.config.js 的输出文件是./dis/bundle.js 和上述的路径/bundle.js 其实是两个文件, 我们通过http://localhost:8080/bundle.js 是能够访问到根目录下的bundle.js 但是上述服务地址打开后却看不到bundle.js文件,而dist目录下的bundle.js文件却是可见的。
也就是说通过wepack-dev-server命令生成的bundle.js并没有存储在物理磁盘上,而是直接托管到了电脑内存中所以我们在项目根目录中找不到这个打包好的bundle.js。我们可以认为wepack-dev-server把打包好的文件以一种虚拟的形式,托管到了项目的根目录中,虽然看不到,但是可以认为和dist、src 、node_modules平级的目录有一个看不见的bundle.js文件。通过这种方式更迅速
package中的scripts: 设置dev 的值为
“dev”: “执行命令 直接打开 指定端口号”
"dev": "webpack-dev-server --open --port 3000 --contentBase src --hot"
可以设置 运行并打开(–open) 指定端口号(–port 3000), 指定默认加载内容的路径( --contentBase src)
–hot 可以实现部分代码的更新而不会重新生成文件,相当于打了一次补丁,同时可以实现浏览器无刷新,即局部刷新。
除了通过上述的方式配置一些命令以外还有一种方式,只是不推荐,作为了解:
“scripts”:{
“dev”:“webpack-dev-server”
}我们把之前的指令以配置文件的方式进行设置,在webpack.config.js文件中进行配置:
const webpack = require('webpack')//启用热更新的第2步 module.exports = { entry: path.join(__dirname,'./src/main.js'), output: { path: path.join(__dirname, './dist'), filename: 'bundle.js' }, devServer:{//这是配置webpack-dev-server命令的第二种方式,相对之前的方式麻烦一些 // "dev": "webpack-dev-server --open --port 3000 --contentBase src --hot" open: true,//自动打开浏览器 port: 3000,//设置启动时的端口 contentBase: 'src',//指定托管的根目录 hot: true //启用热更新 的 第一步设置 }, plugins:[//配置插件的节点 ,在webpack中带s的都是数组,这里是插件数组,这是启用热更新的第3步 new webpack.HotModuleReplacementPlugin()// 实例化一个热更新模块对象,这是启用热更新的第3步 ] }
通过配置html-webpack-plugin插件可以实现不用在html文件中引入bundle.js文件,该插件会自动帮我们引入
第一步:安装插件到本地项目 npm i html-webpack-plugin -D
第二步:配置插件 在wepack.config.js下
const htmlWebpackPlugin = require('htmlWebpackPlugin')//导入在内存中生成HTML页面的插件
module.exports = {
...,
plugins:[
new htmlWebpackPlugin({//创建一个在内存中生成HTML页面的插件对象
template: path.join(__dirname,'./src/index.html'),//指定模板页面,根据该模板指定的文件生成内存中的页面
filename: 'index.html'//指定在内存中生成的页面名称,这里的名称可以自定义,比如index2.html也可以
})
]
}
我们运行后会发现,通过上述内存中的页面的页面源码可以看出,内存中的页面在body 标签中多了一个script标签:
<head>
<script src="/bundle.js"></script>
</head>
<body>
<script type="text/javascript" src="bundle.js"></script><!-- 这一行是多出来的-->
</body>
通过上面的代码,我们可以看出来,其实head标签中的bundle.js那一行是可以注释掉的,因此我们就把它注释掉,发现页面依旧是可以正常运行的。其实第二步的配置就是为了生成页面中这多出来的一行代码
因此在使用了html-webpack-plugin之后,我们不需要手动处理bundle.js的引用路径了,因为该插件已经自动帮我们创建了一个合适的script标签,且引用了一个正确的路径。
html-webpack-plugin插件的作用是 1. 帮我们生成一个内存中的页面,2. 帮我们吧打包好的bundle.js文件追加到页面中去
<link rel="stylesheet" href="css/index.css">
<!-- 这种方式css 会发起二次请求,所以不推荐该方式 -->
需要再配置文件webpack.config.js中进行相关设置
先在main.js中导入css文件
import './css/index.css'
//因为webpack默认只能打包处理js类型的文件,所以上面这行代码保存会报错,不支持该类型的文件。其实严格来说并不是说这行代码报错,是因为去找webpack.config.js中的配置节点module中的rules中三方加载器的配置和处理规则没有找到
webpack默认只能打包处理js类型的文件,如果想要处理其他文件必须要手动安装合适的第三方loader(加载器)
**第1步:如果想要打包处理css文件,需要安装两个加载器:style-loader 和 css-loader **安装命令如下:
npm i style-loader css-loader -D
第二步:在webpack.config.js文件下新增一个配置节点“module”对象,设置其rules属性 ,rules是一个数组,存放了所有第三方文件的匹配和处理规则
module.exports = {
...,
plugins:[
],
module:{//这个节点用于配置所有第三方模块 加载器
rules:[//该数组用于配置 所有第三方模块的匹配规则
{
test:/\.css$/ //以.点开始需要转义,以css结束所以使用$表示结束。 test类似于正则匹配的意思,这里的作用也是匹配 /\.css$/ 作用就是匹配所有.css 结尾的文件
use: ['style-loader','css-loader']//使用哪种第三方规则 ,test是匹配,use 是规则 ,loader的调用规则是从右到左调用
}
]
}
}
加载器加载的顺序是从后往前依次加载的。上面的是先加载css-loader , 然后加载 style-loader ,当最后一个 loader 调用完毕,会把处理的结果直接交给webpack进行打包合并,最终输出到bundle.js中去。
默认情况下webpack无法处理css文件中的url地址,不管是图片还是字体库只要是url地址都处理不了,因此需要第三方加载器来处理 url-loader 同时依赖于file-loader,所以两个都要安装到本地项目:
cnpm i url-loader file-loader -D
module: {
rules: [
{test: /\.(jpg|jpeg|png|gif|bmp)/, use:'url-loader'}// file-loader属于内部依赖,不需要在这里写,所以这里的use只需要一个加载器,因此可以不用数组
]
}
通过rul-loader加载的图片实际是以base64编码方式加载的而不是路径方式,这样减少了二次请求。但是这对于小图片来说是很友好的,大图片则不建议转成base64。
可以通过给加载器添加参数来进行控制,传参的格式和url地址传参一样。也是在后面加上问号拼接参数:
use:'url-loader?limit=7632'//这里的limit的值是字节
会发现在这个限制以内(不包含等于的时候)即小于limit的值的图片大小是以base64方式加载,否则就直接是图片的url地址。
use:'url-loader?limit=7633&name=[name].[ext]'
name=[name].[ext]的意思是原来是什么名称什么后缀名的文件展示的时候保持不变。
但是加载的时候图片其实是载入到当前路径下的,所以会出现重名的现象,因此为了防止图片重名发生的替换问题,在名称面前加入一个哈希值,哈希值是32位的,我们截取几位即可:
use:'url-loader?limit=7623&name=[hash:8]-[name].[ext]'//截取哈希值的前8位区分同名的文件
除了图片url-loader还可以处理字体文件:
{test: /\.(ttf|eot|svg|woff|woff2)$/, use:'url-loader'}//处理字体文件
webpack中默认只能处理一部分的ES6语法,一些更高级的ES6或者ES7的语法webpack是处理不了的,因此需要第三方loader加载器来处理这些高级语法,通过第三方loader转换为低级语法,把结果交给webpack去打包到bundle.js中,通过babel 可以帮我们把高级语法转换为低级语法。
**第一步:**在webpack中可以运行如下两条命令,安装两套包来安装Babel相关的loader功能:
cnpm i babel-core babel-loader babel-plugin-transform-runtime -D //这个命令是安装babel的转换工具
cnpm i babel-preset-env babel-preset-stage-0 -D //安装babel的语法 作用是高版本到底版本的语法转换
这里的 babel-preset-env 是比较新的ES语法,之前我们安装的是 babel-present-es2015 es2015 即 ES6 babel-preset-env 包含了所有和 es*** 相关的语法。
第二步:** 打开webpack配置文件在module节点下rules数组中,添加一个新的匹配规则:
{test:/\.js$/,use:'babel-loader',exclude:/node_modules/}//exclude 排除node_modules目录
在配置babel的loader规则的时候,必须把node_modules目录通过exclude选项排除掉,因为不排除的话babel会把该目录下的所有第三方js文件都打包编译,会非常消耗CPU,同时打包速度会很慢;即便node_module中的js文件被Babel转换了项目也是无法正常运行的。
**第三步:**在项目的根目录下,新建一个为 “.babelrc” 的Babel配置文件,该配置文件属于JSON格式,不能写注释,字符串必须用双引号:presets 语法 plugins 插件,对应的值都是第一步中去除前缀的名称。
{
"presets":["env","stage-0"],
"plugins":["transform-runtime"]
}
var login = {
template:'<h2>这是一个登录组件</h2>'
}
var vm = new Vue({
el: '#app',
data: {},
methods: {},
render:function(createElements){//createElement是一个方法,能够把指定的组件模板渲染为HTML结构
var html = createElements(login);//将上面的组件模板当做参数传入
return html;//这里return的结果,会替换页面中 el 指定的那个容器 相当于把 id 为 app 的那个 div 给删除了,用该组件给替换掉
}
});
使用component属性设置的组件,该组件就相当于一个插值表达式,只是替换当前位置的内容,而render则是删除整个容器,把自己放上去。
在普通网页中使用vue进行开发:
在webpack构建的项目中使用vue进行开发:
在webpack中 的main.js文件中导入vue : import Vue from ‘vue’ //该行的作用是从node_modules包下找到包名vue
import Vue from 'vue' //通过import导入的vue包和以前普通的script方式引入的vue包不一样,它导入的只是运行时(runtime-only)的包,即阉割版,而script引入的是最全面的包,所以这种方式会报错
var vm = new Vue({
})
包的查找规则:
首先去项目根目录下找node_modules-> 根据import from的包名查找对应的 vue 文件夹 -> 查找package.json的包配置文件 -> 查找 main 属性 (main 指定了包被加载时的入口文件) 我们通过这个规则发现main入口是vue.runtime.common.js 文件 而不是vue.js
因此上述的引入方式可以改为:
import Vue from '../node_modules/vue/dist/vue.js'//通过制定路径导入vue.js
或者是通过下面的方式,在webpack的配置文件中加入如下配置:
module.exports = {
...
,
resolve: {
alias:{//修改vue被导入时候的包的路径
"vue$":"vue/dist/vue.js"
}
}
}
通过以上方式修改后,可以使用普通的方式来加载组件了,但是如果不修改,就直接使用Runtime的包怎么实现组件的加载呢
如果使用runtime的包,就不能使用如下的方式来进行组件的引用了
<div id="app">
<login></login>
</div>
<script>
//这里放在main.js文件中
var login ={//声明组件模板
template:'<h2>这是登录组件</h2>'
}
var vm = new Vue({
el: '#app',
data: {
msg:'Hello'
},
components: {
login//注册组件
}
});
</script>
如果想让runtime的包也可以使用的话,就不能使用components属性的方式了,必须要创建一个.vue文件,也就是说使用runtime包去渲染一个组件的话,必须是在.vue文件中进行声明,.vue文件是个纯粹的组件,该文件分三部分组成,HTML模板,js业务逻辑和样式。其中可以包含如下三种标签:
<template>
<!-- 这个标签里写HTML代码-->
<div>
<!--因为一个组件只能有一个跟标签,所以用一个 div 包裹起来-->
<h2>
hello这是一个登录组件 在.vue 文件中设置的
</h2>
</div>
</template>
<script>
//这里写业务逻辑
</script>
<style>
/*这里写样式*/
</style>
在main.js中导入login组件
----------index.html
<div id="app">
<p> {{msg}}</p>
<login></login>
</div>
-----------main.js
<script>
import login from './login.vue'
//这里放在main.js文件中
var vm = new Vue({
el: '#app',
data: {
msg:'Hello'
},
components: {
login//注册组件
}
});
</script>
发现会报错,因为不支持.vue类型文件,因此需要安装三方的loader所以需要进行安装三方loader:
cnpm i vue-loader vue-template-compiler -D
安装完成后在配置文件中新增配置项:
{test:/\.vue$/,use:'vue-loader'}//处理.vue 文件的loader
当时安装完成后依旧提示runtime报错,所以通过vue实例components的方式不行,因此我们可以考虑使用render方式渲染:
import login from './login.vue'
var vm = new Vue({
el:'#app',
data: {},
methods:{},
render: function(createElement){
return createElements(login)
}
});
因此想在webpack中渲染vue的组件需要使用vue实例中的render函数才行。
把render改成箭头函数:
render:(createElement) => {
return createElements(login)
}
//箭头函数如果只有一个参数小括号可以省略,又可以写成:
render: createElement => {
return createElements(login)
}
//参数名称太长 行参可以简写
render: c => {
return c(login)
}
//因为函数体只有一行代码 所以花括号也可以省略掉 ,但是如果省去花括号,默认会return,所以return也可以省略:
render: c => c(login)
也就是说在webpack构建的项目中通过vue实例的render函数进行组件的渲染
第一步:安装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 导入 .vue 创建的组件 : import login from './login.vue’
第六步:创建一个vue实例使用render函数来将组件渲染到页面中的 div 的 id 为 app 的容器中:render: c => c(login)
通过.vue文件中的scritp标签中export default 导出一个对象
<template>
<div>
<h3>
这是登录组件,login.vue --{{msg}}---
</h3>
</div>
</template>
<script>
export default {//通过export default 向外暴露一个对象
data(){//组建中的data必须是一个function 且有返回值,属性中的data必须有return返回值
return {
msg:"123"
};
},
methods:{
show() {
console.log(" login.vue ---- show");
}
}
}
</script>
在ES6中规定的导入和导出模块:
ES6 导入模块使用: import 模块名称 from ‘模块标识符【即模块的包名】’
ES6 导入样式的时候使用: import ‘直接写样式的路径就可以了’
ES6 使用 export default 和 export 来向外暴露成员
export default 向外暴露的成员,可以使用自定义的变量名进行接收,无需跟 export default info 的info 保持一样
export default 在一个模块中只能有一个使用 export default 即默认的只有一个,使用多个的话会报语法错误
除了export 可以暴露成员,ES6好提供了 export var 的方式:
test.js------ export default { infor:'hleda', sdag:'asdgasdg' } export var title = 'this is title' export var content = 'this is content' 只是在接收 export var 这种方式的成员需要使用花括号进行接收 ------ 使用时: import info , { title,content} from './test.js' //import infor2 , { title1 as title,content} from './test.js' //自定义的名称接收的话:title1 as title console.log()
使用 export 向外暴露的成员只能使用花括号来接收,这种形式叫做按需导出,即按照需要进行导出,需要的就用换括号导出,不需要可以不导出,导出时必须要跟暴露的名称一致,不可以自定义,如果想使用自定义的名称的话需要使用 as 关键字起别名的方式。
在Node中向外部暴露成员或导入模块的方式:
- Node导入模块使用: var 名称 = require(‘模块标识符’)
- Node 向外部暴露成员: module.exports 和 exports
module.exports = {}
第一步:导入 vue-router 包: import VueRouter from ‘vue-router’
第二步:手动安装 VueRouter: Vue.use(VueRouter)
第三步:创建路由对象
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
import app from './App.vue'
//导入组件
import account from './main/Account.vue'
import goodslist from './main/GoodsList.vue'
//创建路由对象
var router = new VueRouter({
routes: [
{path: '/account',component: account},
{path: '/goodslist',component: goodslist}
]
})
var vm = new Vue({
el: '#app',
render: c => c(app),
router //将路由对象挂载到vue实例上
})
因为render方法,会把el指定的容器中的所有内容都清空覆盖,所以不要把路由的router-view 和 router-link直接写到el控制的元素中,只能将其放到组件中去,因此把 account 和 goodslist 的路由放在 App.vue 中去:
App.vue
<template>
<div>
<router-link to="/account">Account</router-link>
<router-link to="/goodslist">Goodslist</router-link>
<router-view></router-view>
</div>
</template>
<script></script>
<style></style>
因为 render 渲染的是 el 指定的元素,account和goodlist 组件是通过路由匹配监听到的,所以这两个组件只能展示到属于路由的中去
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
import app from './App.vue'
//导入组件
import account from './main/Account.vue'
import goodslist from './main/GoodsList.vue'
//导入子路由的组件
import login from './subcom/login.vue'
import register from './subcom/register.vue'
//创建路由对象
var router = new VueRouter({
routes: [
{
path: '/account',
component: account,
children: [
{path: 'login' , component: login},
{path: 'register', component: register}
]
},
{path: '/goodslist',component: goodslist}
]
})
var vm = new Vue({
el: '#app',
render: c => c(app),
router //将路由对象挂载到vue实例上
})
在style中写标签选择器,会发现更改子级路由的组件的样式会使整个页面也就是说父级也被随之改变了,因此需要增加一个style的属性 scoped
<style scoped>
div {
color: red;
}
</style>
style 标签默认只支持 css 的语法格式,如果想要支持 scss 或 less 语法,需要为 style 元素 设置 lang 属性:
<style lang="scss" scoped>//此时,加上 lang="scss" 下面的书写方式才不会报错 ,但是同时要加上scoped 才不会是父级改变样式
body {
div {
}
}
</style>
.vue 组件中定义的style标签一定要加 scoped,scoped的实现原理是通过CSS的属性选择器实现的。
将路由的导入和路由对象的创建抽离出来
新建一个router.js文件:
import VueRouter from 'vue-router'
//导入组件
import account from './main/Account.vue'
import goodslist from './main/GoodsList.vue'
//导入子路由的组件
import login from './subcom/login.vue'
import register from './subcom/register.vue'
//创建路由对象
var router = new VueRouter({
routes: [
{path: '/account',component: account},
{path: '/goodslist',component: goodslist}
]
})
//通过ES6的暴露方式 将router暴露出去
export default router
main.js:
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
import app from './App.vue'
//导入自定义路由模块
import router form './router.js'
var vm = new Vue({
el: '#app',
render: c => c(app),
router //将路由对象挂载到vue实例上
})