库,本质上是一些函数的集合。每次调用函数,实现一个特定的功能,接着把
控制权
交给使用者
框架,是一套完整的解决方案,使用框架的时候,须要把你的代码放到框架合适的地方,框架会在合适的时机调用你的代码
核心点:谁起到主导做用(控制反转)javascript
MVVM经过数据双向绑定
让数据自动地双向同步css
虽然没有彻底遵循 MVVM 模型,Vue 的设计无疑受到了它的启发。所以在文档中常常会使用 vm (ViewModel 的简称) 这个变量名表示 Vue 实例
npm i -S vue
<!-- 指定vue管理内容区域,须要经过vue展现的内容都要放到找个元素中 一般咱们也把它叫作边界 数据只在边界内部解析--> <div id="app">{{ msg }}</div> <!-- 引入 vue.js --> <script src="vue.js"></script> <!-- 使用 vue --> <script> var vm = new Vue({ // el:提供一个在页面上已存在的 DOM 元素做为 Vue 实例的挂载目标 el: '#app', // Vue 实例的数据对象,用于给 View 提供数据 data: { msg: 'Hello Vue' } }) </script>
vm.$data
访问到data中的全部属性,或者 vm.msg
var vm = new Vue({ data: { msg: '你们好,...' } }) vm.$data.msg === vm.msg // true
Mustache(插值语法)
,也就是 {{}}
语法{{}}
从数据对象data
中获取数据{{}}
中只能出现JavaScript表达式 而不能解析js语句<h1>Hello, {{ msg }}.</h1> <p>{{ 1 + 2 }}</p> <p>{{ isOk ? 'yes': 'no' }}</p> <!-- !!!错误示范!!! --> <h1 title="{{ err }}"></h1>
双向数据绑定:将DOM与Vue实例的data数据绑定到一块儿,彼此之间相互影响html
原理:Object.defineProperty
中的get
和set
方法前端
getter
和setter
:访问器读取或设置
对象属性值的时候,执行的操做/* defineProperty语法 介绍 */ var obj = {} Object.defineProperty(obj, 'msg', { // 设置 obj.msg = "1" 时set方法会被系统调用 参数分别是设置后和设置前的值 set: function (newVal, oldVal) { }, // 读取 obj.msg 时get方法会被系统调用 get: function ( newVal, oldVal ) {} })
<!-- 示例 --> <input type="text" id="txt" /> <span id="sp"></span> <script> var txt = document.getElementById('txt'), sp = document.getElementById('sp'), obj = {} // 给对象obj添加msg属性,并设置setter访问器 Object.defineProperty(obj, 'msg', { // 设置 obj.msg 当obj.msg反生改变时set方法将会被调用 set: function (newVal) { // 当obj.msg被赋值时 同时设置给 input/span txt.value = newVal sp.innerText = newVal } }) // 监听文本框的改变 当文本框输入内容时 改变obj.msg txt.addEventListener('keyup', function (event) { obj.msg = event.target.value }) </script>
data
中的数据才是响应式的,动态添加进来的数据默认为非响应式能够经过如下方式实现动态添加数据的响应式vue
Vue.set(object, key, value)
- 适用于添加单个属性Object.assign()
- 适用于添加多个属性var vm = new Vue({ data: { stu: { name: 'jack', age: 19 } } }) /* Vue.set */ Vue.set(vm.stu, 'gender', 'male') /* Object.assign 将参数中的全部对象属性和值 合并到第一个参数 并返回合并后的对象*/ vm.stu = Object.assign({}, vm.stu, { gender: 'female', height: 180 })
若是须要那到更新后dom中的数据 则须要经过 Vue.nextTick(callback)
:在DOM更新后,执行某个操做(属于DOM操做)java
vm.$nextTick(function () {})
methods: { fn() { this.msg = 'change' this.$nextTick(function () { console.log('$nextTick中打印:', this.$el.children[0].innerText); }) console.log('直接打印:', this.$el.children[0].innerText); } }
v-
前缀的特殊属性<h1 v-text="msg"></h1>
<h1 v-html="msg"></h1>
v-bind:title="msg"
:title="msg"
<!-- 完整语法 --> <a v-bind:href="url"></a> <!-- 缩写 --> <a :href="url"></a>
v-on:click="say"
or v-on:click="say('参数', $event)"
@click="say"
methods
<!-- 完整语法 --> <a v-on:click="doSomething"></a> <!-- 缩写 --> <a @click="doSomething"></a>
.stop
阻止冒泡,调用 event.stopPropagation().prevent
阻止默认行为,调用 event.preventDefault().capture
添加事件侦听器时使用事件捕获
模式.self
只当事件在该元素自己(好比不是子元素)触发时,才会触发事件.once
事件只触发一次<input type="text" v-model="message" placeholder="edit me"> <p>Message is: {{ message }}</p>
<!-- 1 基础用法 --> <div v-for="item in items"> {{ item.text }} </div> <!-- item 为当前项,index 为索引 --> <p v-for="(item, index) in list">{{item}} -- {{index}}</p> <!-- item 为值,key 为键,index 为索引 --> <p v-for="(item, key, index) in obj">{{item}} -- {{key}}</p> <p v-for="item in 10">{{item}}</p>
v-for
的时候提供 key
属性,以得到性能提高。<div v-for="item in items" :key="item.id"> <!-- 内容 --> </div>
v-bind:class="expression"
or :class="expression"
<!-- 1 --> <div v-bind:class="{ active: true }"></div> ===> 解析后 <div class="active"></div> <!-- 2 --> <div :class="['active', 'text-danger']"></div> ===>解析后 <div class="active text-danger"></div> <!-- 3 --> <div v-bind:class="[{ active: true }, errorClass]"></div> ===>解析后 <div class="active text-danger"></div> --- style --- <!-- 1 --> <div v-bind:style="{ color: activeColor, 'font-size': fontSize + 'px' }"></div> <!-- 2 将多个 样式对象 应用到一个元素上--> <!-- baseStyles 和 overridingStyles 都是data中定义的对象 --> <div v-bind:style="[baseStyles, overridingStyles]"></div>
v-if
:根据表达式的值的真假条件,销毁或重建元素v-show
:根据表达式之真假值,切换元素的 display CSS 属性<p v-show="isShow">这个元素展现出来了吗???</p> <p v-if="isShow">这个元素,在HTML结构中吗???</p>
<span v-pre>{{ this will not be compiled }}</span>
<span v-once>This will never change: {{msg}}</span>
{{}}
和 v-bind 表达式Vue.filter('filterName', function (value) { // value 表示要过滤的内容 })
<div>{{ dateStr | date }}</div> <div>{{ dateStr | date('YYYY-MM-DD hh:mm:ss') }}</div> <script> Vue.filter('date', function(value, format) { // value 要过滤的字符串内容,好比:dateStr // format 过滤器的参数,好比:'YYYY-MM-DD hh:mm:ss' }) </script>
{ data: {}, // 经过 filters 属性建立局部过滤器 // 注意:此处为 filters filters: { filterName: function(value, format) {} } }
v-on
在监听键盘事件时添加关键修饰符// 只有在 keyCode 是 13 时调用 vm.submit() @keyup.13="submit" // 使用全局按键别名 @keyup.enter="add" --- // 经过全局 config.keyCodes 对象自定义键值修饰符别名 Vue.config.keyCodes.f2 = 113 // 使用自定义键值修饰符 @keyup.enter.f2="add"
watch
是一个对象,键是须要观察的表达式,值是对应回调函数new Vue({ data: { a: 1, b: { age: 10 } }, watch: { a: function(val, oldVal) { // val 表示当前值 // oldVal 表示旧值 console.log('当前值为:' + val, '旧值为:' + oldVal) }, // 监听对象属性的变化 b: { handler: function (val, oldVal) { /* ... */ }, // deep : true表示是否监听对象内部属性值的变化 deep: true }, // 只监视user对象中age属性的变化 'user.age': function (val, oldVal) { }, } })
computed
中的属性不能与data
中的属性同名,不然会报错 var vm = new Vue({ el: '#app', data: { firstname: 'jack', lastname: 'rose' }, computed: { fullname() { return this.firstname + '.' + this.lastname } } })
生命周期钩子函数的定义:从组件被建立,到组件挂载到页面上运行,再到页面关闭组件被卸载,这三个阶段老是伴随着组件各类各样的事件,这些事件,统称为组件的生命周期函数!node
生命周期钩子函数
,咱们只须要提供这些钩子函数便可Promise based HTTP client for the browser and node.jsreact
npm i -S axios
// 在浏览器中使用,直接引入js文件使用下面的GET/POST请求方式便可 // 1 引入 axios.js // 2 直接调用axios提供的API发送请求 created: function () { axios.get(url) .then(function(resp) {}) } --- // 配合 webpack 使用方式以下: import Vue from 'vue' import axios from 'axios' // 将 axios 添加到 Vue.prototype 中 Vue.prototype.$axios = axios --- // 在组件中使用: methods: { getData() { this.$axios.get('url') .then(res => {}) .catch(err => {}) } } --- // API使用方式: axios.get(url[, config]) axios.post(url[, data[, config]]) axios(url[, config]) axios(config)
const url = 'http://vue.studyit.io/api/getnewslist' // url中带有query参数 axios.get('/user?id=89') .then(function (response) { console.log(response); }) .catch(function (error) { console.log(error); }); // url和参数分离,使用对象 axios.get('/user', { params: { id: 12345 } })
application/x-www-form-urlencoded
格式发送请求,咱们能够这样:// 使用 qs 包,处理将对象序列化为字符串 // npm i -S qs // var qs = require('qs') import qs from 'qs' qs.stringify({ 'bar': 123 }) ===> "bar=123" axios.post('/foo', qs.stringify({ 'bar': 123 })) // 或者: axios.post('/foo', 'bar=123&age=19')
const url = 'http://vue.studyit.io/api/postcomment/17' axios.post(url, 'content=点个赞不过份') axios.post('/user', qs.stringify({ firstName: 'Fred', lastName: 'Flintstone' })) .then(function (response) { console.log(response); }) .catch(function (error) { console.log(error); });
// 设置请求公共路径: axios.defaults.baseURL = 'http://vue.studyit.io'
request
中的函数,请求发送完成以后执行response
中的函数// 请求拦截器 axios.interceptors.request.use(function (config) { // 全部请求以前都要执行的操做 return config; }, function (error) { // 错误处理 return Promise.reject(error); }); // 响应拦截器 axios.interceptors.response.use(function (response) { // 全部请求完成后都要执行的操做 return response; }, function (error) { // 错误处理 return Promise.reject(error); });
// 第一个参数:指令名称 // 第二个参数:配置对象,指定指令的钩子函数 Vue.directive('directiveName', { // bind中只能对元素自身进行DOM操做,而没法对父级元素操做 // 只调用一次 指令第一次绑定到元素时调用。在这里能够进行一次性的初始化设置。 bind( el,binding, vnode ) { // 参数详解 // el:指令所绑定的元素,能够用来直接操做 DOM 。 // binding:一个对象,包含如下属性: // name:指令名,不包括 v- 前缀。 // value:指令的绑定值,等号后面的值 。 // oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。不管值是否改变均可用。 // expression:字符串形式的指令表达式 等号后面的字符串 形式 // arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"。 // modifiers:指令修饰符。例如:v-directive.foo.bar中,修饰符对象为 { foo: true, bar: true }。 // vnode:Vue 编译生成的虚拟节点。。 // oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。 }, // inserted这个钩子函数调用的时候,当前元素已经插入页面中了,也就是说能够获取到父级节点了 inserted ( el,binding, vnode ) {}, // DOM从新渲染前 update(el,binding, vnode,oldVnode) {}, // DOM从新渲染后 componentUpdated ( el,binding, vnode,oldVnode ) {}, // 只调用一次,指令与元素解绑时调用 unbind ( el ) { // 指令所在的元素在页面中消失,触发 } }) // 简写 若是你想在 bind 和 update 时触发相同行为,而不关心其它的钩子: Vue.directive('自定义指令名', function( el, binding ) {}) // 例: Vue.directive('color', function(el, binding) { el.style.color = binging.value }) // 使用 注意直接些会被i成data中的数据“red” 须要字符串则嵌套引号"'red'" <p v-color="'red'"></p>
var vm = new Vue({ el : "#app", directives: { directiveName: { } } })
组件系统是 Vue 的另外一个重要概念,由于它是一种抽象,容许咱们使用小型、独立和一般可复用的组件构建大型应用。仔细想一想,几乎任意类型的应用界面均可以抽象为一个组件树
// 1 注册全局组件 Vue.component('my-component', { // template 只能有一个根元素 template: '<p>A custom component!</p>', // 组件中的 `data` 必须是函数 而且函数的返回值必须是对象 data() { return { msg: '注意:组件的data必须是一个函数!!!' } } }) // 2 使用:以自定义元素的方式 <div id="example"> <my-component></my-component> </div> // =====> 渲染结果 <div id="example"> <p>A custom component!</p> </div> // 3 template属性的值能够是: - 1 模板字符串 - 2 模板id template: '#tpl' <script type="text/x-template" id="tpl"> <p>A custom component!</p> </script>
extend
:使用基础 Vue 构造器,建立一个“子类”。参数是一个包含组件选项的对象。// 注册组件,传入一个扩展过的构造器 Vue.component('my-component', Vue.extend({ /* ... */ })) // 注册组件,传入一个选项对象 (自动调用 Vue.extend) Vue.component('my-component', { /* ... */ }) var Home = Vue.extend({ template: '', data() {} }) Vue.component('home', Home)
var Child = { template: '<div>A custom component!</div>' } new Vue({ // 注意:此处为 components components: { // <my-component> 将只在当前vue实例中使用 // my-component 为组件名 值为配置对象 'my-component': { template: ``, data () { return { } }, props : [] } } })
在某些特定的标签中只能存在指定表恰 如ul > li 若是要浏览器正常解析则须要使用is
<!-- 案例 --> <ul id="app"> <!-- 不能识别 --> <my-li></my-li> 正常识别 <li is="my-li"></li> </ul> <script> var vm = new Vue({ el: "#app", components : { myLi : { template : `<li>内容</li>` } } }) </script>
props
属性来传递数据 props是一个数组props
属性显示指定,不然,不会生效props
属性的用法与data
属性的用法相同<div id="app"> <!-- 若是须要往子组件总传递父组件data中的数据 须要加v-bind="数据名称" --> <hello v-bind:msg="info"></hello> <!-- 若是传递的是字面量 那么直接写--> <hello my-msg="abc"></hello> </div> <!-- js --> <script> new Vue({ el: "#app", data : { info : 15 }, components: { hello: { // 建立props及其传递过来的属性 props: ['msg', 'myMsg'], template: '<h1>这是 hello 组件,这是消息:{{msg}} --- {{myMsg}}</h1>' } } }) </script>
方式:父组件给子组件传递一个函数,由子组件调用这个函数jquery
步骤:webpack
$emit()
触发自定义事件事件 this.$emit(pfn,参数列表。。。)<hello @pfn="parentFn"></hello> <script> Vue.component('hello', { template: '<button @click="fn">按钮</button>', methods: { // 子组件:经过$emit调用 fn() { this.$emit('pfn', '这是子组件传递给父组件的数据') } } }) new Vue({ methods: { // 父组件:提供方法 parentFn(data) { console.log('父组件:', data) } } }) </script>
在简单的场景下,可使用一个空的 Vue 实例做为事件总线
$on()
:绑定自定义事件var bus = new Vue() // 在组件 B 绑定自定义事件 bus.$on('id-selected', function (id) { // ... }) // 触发组件 A 中的事件 bus.$emit('id-selected', 1)
<!-- 组件A: --> <com-a></com-a> <!-- 组件B: --> <com-b></com-b> <script> // 中间组件 var bus = new Vue() // 通讯组件 var vm = new Vue({ el: '#app', components: { comB: { template: '<p>组件A告诉我:{{msg}}</p>', data() { return { msg: '' } }, created() { // 给中间组件绑定自定义事件 注意:若是用到this 须要用箭头函数 bus.$on('tellComB', (msg) => { this.msg = msg }) } }, comA: { template: '<button @click="emitFn">告诉B</button>', methods: { emitFn() { // 触发中间组件中的自定义事件 bus.$emit('tellComB', '土豆土豆我是南瓜') } } } } }) </script>
案例:
<!-- html代码 --> <div id="app"> <hello> <!-- 若是只有一个slot插槽 那么不须要指定名称 --> <p slot="插槽名称">我是额外的内容</p> </hello> </div>
// js代码 new vue({ el : "#app", components : { hello : { template : ` <div> <p>我是子组件中的内容</p> <slot name="名称"></slot> </div> ` } } })
vm.$refs
一个对象,持有已注册过 ref 的全部子组件(或HTML元素)ref
属性,而后在JS中经过vm.$refs.属性
来获取<div id="app"> <div ref="dv"></div> <my res="my"></my> </div> <!-- js --> <script> new Vue({ el : "#app", mounted() { this.$refs.dv //获取到元素 this.$refs.my //获取到组件 }, components : { my : { template: `<a>sss</a>` } } }) </script>
单页Web应用(single page application,SPA),就是只有一个Web页面的应用, 是加载单个HTML页面,并在用户与应用程序交互时动态更新该页面的Web应用程序。
单页面应用程序:
传统多页面应用程序:
优点
实现思路和技术点
在 Web app 中,经过一个页面来展现和管理整个应用的功能。
SPA每每是功能复杂的应用,为了有效管理全部视图内容,前端路由 应运而生!
简单来讲,路由就是一套映射规则(一对一的对应规则),由开发人员制定规则。
当URL中的哈希值(# hash)发生改变后,路由会根据制定好的规则,展现对应的视图内容
<div id="app"> <!-- 5 路由入口 指定跳转到只定入口 --> <router-link to="/home">首页</router-link> <router-link to="/login">登陆</router-link> <!-- 7 路由出口:用来展现匹配路由视图内容 --> <router-view></router-view> </div> <!-- 1 导入 vue.js --> <script src="./vue.js"></script> <!-- 2 导入 路由文件 --> <script src="./node_modules/vue-router/dist/vue-router.js"></script> <script> // 3 建立两个组件 const Home = Vue.component('home', { template: '<h1>这是 Home 组件</h1>' }) const Login = Vue.component('login', { template: '<h1>这是 Login 组件</h1>' }) // 4 建立路由对象 const router = new VueRouter({ routes: [ // 路径和组件一一对应 { path: '/home', component: Home }, { path: '/login', component: Login } ] }) var vm = new Vue({ el: '#app', // 6 将路由实例挂载到vue实例 router }) </script>
// 将path 重定向到 redirect { path: '/', redirect: '/home' }
路由导航高亮
匹配路由模式
new Router({ routers:[], mode: "hash", //默认hash | history 能够达到隐藏地址栏hash值 | abstract,若是发现没有浏览器的 API 则强制进入 linkActiveClass : "now" //当前匹配的导航连接将被自动添加now类 })
// 方式一 <router-link to="/user/1001">若是你须要在模版中使用路由参数 能够这样 {{$router.params.id}}</router-link> // 方式二 <router-link :to="{path:'/user',query:{name:'jack',age:18}}">用户 Rose</router-link> <script> // 路由 var router = new Router({ routers : [ // 方式一 注意 只有/user/1001这种形式能被匹配 /user | /user/ | /user/1001/ 都不能被匹配 // 未来经过$router.params获取参数返回 {id:1001} { path: '/user/:id', component: User }, // 方式二 { path: "user" , component: User} ] }) // User组件: const User = { template: `<div>User {{ $route.params.id }}</div>` } </script> <!-- 若是要子啊vue实例中获取路由参数 则使用this.$router.params 获取路由参数对象 --> <!-- {{$router.query}} 获取路由中的查询字符串 返回对象 -->
// 父组件: const User = Vue.component('user', { template: ` <div class="user"> <h2>User Center</h2> <router-link to="/user/profile">我的资料</router-link> <router-link to="/user/posts">岗位</router-link> <!-- 子路由展现在此处 --> <router-view></router-view> </div> ` }) // 子组件[简写] const UserProfile = { template: '<h3>我的资料:张三</h3>' } const UserPosts = { template: '<h3>岗位:FE</h3>' } // 路由 var router =new Router({ routers : [ { path: '/user', component: User, // 子路由配置: children: [ { // 当 /user/profile 匹配成功, // UserProfile 会被渲染在 User 的 <router-view> 中 path: 'profile', component: UserProfile }, { // 当 /user/posts 匹配成功 // UserPosts 会被渲染在 User 的 <router-view> 中 path: 'posts', component: UserPosts } ] } ] })
为何须要模块化
AMD 浏览器端
CommonJS nodejs
CMD 浏览器端
AMD 的使用
Asynchronous Module Definition:异步模块定义,浏览器端模块开发的规范 表明:require.js 特色:模块被异步加载,模块加载不影响后面语句的运行
一、定义模块
// 语法:define(name, dependencies?, factory); // name表示:当前模块的名称,是一个字符串 无关紧要 // dependencies表示:当前模块的依赖项,是一个数组不管依赖一项仍是多项 无则不写 // factory表示:当前模块要完成的一些功能,是一个函数 // 定义对象模块 define({}) // 定义方法模块 define(function() { return {} }) // 定义带有依赖项的模块 define(['js/a'], function() {})
二、加载模块
// - 注意:require的第一个参数必须是数组 // 参数必须是数组 表示模块路径 以当前文件为基准,经过回调函数中的参数获取加载模块中的变量 参数与模块按照顺序一一对应 require(['a', 'js/b'], function(a, b) { // 使用模块a 和 模块b 中的代码 })
三、路径查找配置
// 配置示例 // 注意配置应当在使用以前 require.config({ baseUrl: './js' // 配置基础路径为:当前目录下的js目录 }) require(['a']) // 查找 基础路径下的 ./js/a.js // 简化加载模块路径 require.config({ baseUrl: './js', // 配置一次便可,直接经过路径名称(template || jquery)加载模块 paths: { template: 'assets/artTemplate/template-native', jquery: 'assets/jquery/jquery.min' } }) // 加载jquery template模块 require(['jquery', 'template'])
四、非模块化和依赖项支持
// 示例 require.config({ baseUrl: './js', paths: { // 配置路径 noModule: 'assets/demo/noModule' }, // 配置不符合规范的模块项 shim: { // 模块名称 noModule: { deps: [], // 依赖项 exports: 'sayHi' // 导出模块中存在的函数或变量 } } }); // 注意点 若是定义模块的时候,指定了模块名称,须要使用该名称来引用模块 // 定义 这个模块名称与paths中的名称相同 define('moduleA', function() {}) // 导入 require.config({ paths: { // 此处的模块名:moduleA moduleA: 'assets/demo/moduleA' } })
五、路径加载规则
路径配置的优先级:
<!-- 设置data-main属性 1 data-main属性指定的文件也会同时被加载 2 用于指定查找其余模块的基础路径 --> <script src="js/require.js" data-main="js/main"></script>
[ˈbʌndl]
捆绑,收集,归拢,把…塞入1 webpack 将带有依赖项的各个模块打包处理后,变成了独立的浏览器可以识别的文件 2 webpack 合并以及解析带有依赖项的模块
webpack 是一个现代 JavaScript 应用程序的模块打包器(特色 module、 bundler)
webpack 是一个 模块化方案(预编译)
webpack获取具备依赖关系的模块,并生成表示这些模块的静态资源
对比 模块化方案: webpack 和 requirejs(经过编写代码的方式将前端的功能,划分红独立的模块) browserify 是与 webpack 类似的模块化打包工具 webpack 预编译 (在开发阶段经过webpack进行模块化处理, 最终项目上线, 就不在依赖于 webpack) requirejs 线上的编译( 代码运行是须要依赖与 requirejs 的 )
webpack解决了现存模块打包器的两个痛点:
JS的模块化规范:
import
export
require()
module.exports
define
和 require
非JS等静态资源:
@import
url(...)
或 HTML <img src=...>
全局安装:npm i -g webpack
webpack
这个命令项目安装:npm i -D webpack
npm i -D webpack
webpack.config.js
)npm init -y
初始package.json,使用npm来管理项目中的包index.html
和index.js
,实现隔行变色功能webpack src/js/index.js dist/bundle.js
进行打包构建,语法是:webpack 入口文件 输出文件
/* src/js/index.js */ // 1 导入 jQuery import $ from 'jquery' // 2 获取页面中的li元素 const $lis = $('#ulList').find('li') // 3 隔行变色 // jQuery中的 filter() 方法用来过滤jquery对象 $lis.filter(':odd').css('background-color', '#def') $lis.filter(':even').css('background-color', 'skyblue') //命令行运行 `webpack src/js/index.js dist/bundle.js 目录生成在命令行运行目录 /* 运行流程: 一、webpack 根据入口找到入口文件 二、分析js中的模块化语法 三、将全部关联文件 打包合并输出到出口 */
npm i -D webpack-dev-server
webpack-dev-server
,须要经过 package.json
的 scripts
实现npm run dev
// 参数解释 注意参数是无序的 有值的参数空格隔开 // --open 自动打开浏览器 // --contentBase ./ 指定浏览器 默认打开的页面路径中的 index.html 文件 // --open 自动打开浏览器 // --port 8080 端口号 // --hot 热更新,只加载修改的文件(按需加载修改的内容),而非所有加载 "scripts": { "dev": "webpack-dev-server --open --contentBase ./ --port 8080 --hot" }
var path = require('path') module.exports = { // 入口文件 entry: path.join(__dirname, 'src/js/index.js'), // 输出文件 output: { path: path.join(__dirname, 'dist'), // 输出文件的路径 filename: 'bundle.js' // 输出文件的名称 } } const webpack = require('webpack') devServer: { // 服务器的根目录 Tell the server where to serve content from // https://webpack.js.org/configuration/dev-server/#devserver-contentbase contentBase: path.join(__dirname, './'), // 自动打开浏览器 open: true, // 端口号 port: 8888, // --------------- 1 热更新 ----------------- hot: true }, plugins: [ // ---------------- 2 启用热更新插件 ---------------- new webpack.HotModuleReplacementPlugin() ]
html-webpack-plugin 插件
npm i -D html-webpack-plugin
bundle.js
、css
等文件/* webpack.config.js */ const htmlWebpackPlugin = require('html-webpack-plugin') plugins: [ new htmlWebpackPlugin({ // 模板页面路径 template: path.join(__dirname, './index.html'), // 在内存中生成页面路径,默认值为:index.html filename: 'index.html' }) ]
webpack enables use of loaders to preprocess files. This allows you to bundle any static resource way beyond JavaScript.
一、 CSS打包
npm i -D style-loader css-loader
/* 在index.js 导入 css 文件*/ import './css/app.css' /* webpack.config.js 配置各类资源文件的loader加载器*/ module: { // 配置匹配规则 rules: [ // test 用来配置匹配文件规则(正则) // use 是一个数组,按照从后往前的顺序执行加载 {test: /\.css$/, use: ['style-loader', 'css-loader']}, ] }
二、 使用webpack打包sass文件
npm i -D sass-loader node-sass
sass-loader
依赖于 node-sass
模块/* webpack.config.js */ // 参考:https://webpack.js.org/loaders/sass-loader/#examples // "style-loader" :creates style nodes from JS strings 建立style标签 // "css-loader" :translates CSS into CommonJS 将css转化为CommonJS代码 // "sass-loader" :compiles Sass to CSS 将Sass编译为css module:{ rules:[ {test: /\.(scss|sass)$/, use: ['style-loader', 'css-loader', 'sass-loader']}, ] }
三、 图片和字体打包
npm i -D url-loader file-loader
file-loader
:加载并重命名文件(图片、字体 等)url-loader
:将图片或字体转化为base64编码格式的字符串,嵌入到样式文件中/* webpack.config.js */ module: { rules:[ // 打包 图片文件 { test: /\.(jpg|png|gif|jpeg)$/, use: 'url-loader' }, // 打包 字体文件 { test: /\.(woff|woff2|eot|ttf|otf)$/, use: 'file-loader' } ] }
limit
参数的做用:(单位为:字节(byte))
小于
指定的limit时,图片被转化为base64编码格式大于等于
指定的limit时,图片被重命名以url路径形式加载(此时,须要file-loader
来加载图片)/* webpack.config.js */ module: { rules: [ // {test: /\.(jpg|png|gif|jpeg)$/, use: 'url-loader?limit=100'}, { test: /\.(jpg|png|gif|jpeg)$/, use: [ { loader: 'url-loader', options: { limit: 8192 } } ] } ] }
file-loader
或url-loader
npm i -D babel-core babel-loader
npm i -D babel-preset-env
/* webpack.config.js */ module: { rules: [ // exclude 排除,不须要编译的目录,提升编译速度 {test: /\.js$/, use: 'babel-loader', exclude: /node_modules/} ] }
.babelrc
配置文件/* 建立 .babelrc 文件*/ // 未来babel-loader运行的时候,会检查这个配置文件,并读取相关的语法和插件配置 { "presets": ["env"] }
babel的做用:
Babel经过语法转换器,可以支持最新版本的JavaScript语法
babel-preset-* 用来指定咱们书写的是什么版本的JS代码
Stage 0 - Strawman(展现阶段) Stage 1 - Proposal(征求意见阶段) Stage 2 - Draft(草案阶段) Stage 3 - Candidate(候选人阶段) Stage 4 - Finished(定案阶段) Stage 0 is "i've got a crazy idea", stage 1 is "this idea might not be stupid", stage 2 is "let's use polyfills and transpilers to play with it", stage 3 is "let's let browsers implement it and see how it goes", stage 4 is "now it's javascript".
做用:实现浏览器对不支持API的兼容(兼容旧环境、填补)
'abc'.padStart(10)
npm i -S babel-polyfill
方式二:npm i -D babel-plugin-transform-runtime
和 npm i -S babel-runtime
区别: polyfill 全部兼容性问题,均可以经过polyfill解决(包括:实例方法)、污染全局环境 runtime 除了实例方法之外,其余兼容新问题都能解决、不污染全局环境 polyfill:若是想要支持全局对象(好比:`Promise`)、静态方法(好比:`Object.assign`)或者**实例方法**(好比:`String.prototype.padStart`)等,那么就须要使用`babel-polyfill` babel-runtime :提供了兼容旧环境的函数,使用的时候,须要咱们本身手动引入 好比: const Promise = require('babel-runtime/core-js/promise') 存在的问题: 1 手动引入太繁琐 2 多个文件引入同一个helper(定义),形成代码重复,增长代码体积 babel-plugin-transform-runtime: 1 自动引入helper(好比,上面引入的 Promise) 2 babel-runtime提供helper定义,引入这个helper便可使用,避免重复 3 依赖于 babel-runtime 插件 transform-runtime插件的使用: 直接在 .bablerc 文件中,添加一个 plugins 的配置项便可!!! "plugins": [ "transform-runtime" ]
/* babel-polyfill 的使用步骤: 1 main.js */ // 第一行引入 require("babel-polyfill") var s = 'abc'.padStart(4) console.log(s) // 2 webpack.config.js 配置 module.exports = { entry: ['babel-polyfill', './js/main.js'] }
babel-core babel核心包 babel-loader 用来解析js文件 babel-preset-* 新ES语法的解析和转换 transform-runtime / babel-polyfill 兼容旧浏览器,到达支持新API目的 // 判断浏览器是否兼容 padStart 这个 API if (!String.prototype.padStart) { // 若是不兼容, 就本身模拟 padStart的功能实现一份 String.prototype.padStart = function padStart(targetLength,padString) { } }
.vue
,该文件须要被预编译后才能在浏览器中使用npm i -D vue-loader vue-template-compiler
<!-- App.vue 示例代码: --> <template> <div> <h1>VUE 单文件组件示例 -- App.vue</h1> <p>这是 模板内容</p> </div> </template> <script> // 组件中的逻辑代码 export default {} </script> <style> /* 组件样式 */ h1 { color: red; } </style>
// webpack.config.js 配置: module: { rules: [ { test: /\.vue$/, loader: 'vue-loader' } ] }
/* main.js */ import Vue from 'vue' // 导入 App 组件 import App from './App.vue' const vm = new Vue({ el: '#app', // 经过 render 方法,渲染App组件 render: c => c(App) })
npm i -D vue-loader vue-template-compiler
2 在 webpack.config.js
中配置 .vue
文件的loader
{ test: /\.vue$/, use: 'vue-loader' }
App.vue
单文件组件,注意:App能够是任意名称main.js
入口文件中,导入 vue
和 App.vue
组件,经过 render 将组件与实例挂到一块儿import Vue from 'vue' import App from './App.vue' // ------------- vue路由配置 开始 -------------- import Home from './components/home/Home.vue' import Login from './components/login/Login.vue' // 1 导入 路由模块 import VueRouter from 'vue-router' // 2 ** 调用use方法使用插件 ** Vue.use(VueRouter) // 3 建立路由对象 const router = new VueRouter({ routes: [ { path: '/home', component: Home }, { path: '/login', component: Login } ] }) // ------------- vue路由配置 结束 -------------- const vm = new Vue({ el: '#app', render: c => c(App), // 4 挂载到 vue 实例中 router })
npm i -S mint-ui
// 1 导入 mint-ui模块 import MintUI from 'mint-ui' // 2 导入 样式 import 'mint-ui/lib/style.css' // 3 注册插件 Vue.use(MintUI)
// 只须要导入 MUI的样式 便可,根据MUI的例子,直接使用HTML结果便可 // 导入样式 import './lib/mui/css/mui.min.css'
npm i -S element-ui
{ "presets": [ ["es2015", { "modules": false }], "stage-0" ], "plugins": [ ["component", [ { "libraryName": "mint-ui", "style": true }, { "libraryName": "element-ui", "styleLibraryName": "theme-default" } ]] ] }
webpack
命令可以生成dist目录到磁盘中,最终,把打包后的代码,部署服务器中去webpack-dev-server
仅是在内存中生成的文件,并无写到磁盘中,因此,只能在开发期间使用webpack.config.js
webpack.prod.js
(文件名称非固定 production 生产环境)webpack --config webpack.prod.js
指定配置文件名称运行webpack--display-error-details
用于显示webpack打包的错误信息/* package.json */ "scripts": { "build": "webpack --config webpack.prod.js" }
1 在项目根目录中建立 webpack.prod.js 文件 2 在 package.json 中, 配置一个 scripts 3 在 终端中 经过 npm run build 对项目进行打包
1 删除掉 devServer 相关的配置项 2 将图片和字体文件输出到指定的文件夹中 3 自动删除dist目录 4 分离第三方包(将使用的vue等第三方包抽离到 vender.js 中) 5 压缩混淆JS 以及 指定生成环境 6 抽取和压缩CSS文件 7 压缩HTML页面 8 配合vue的异步组件,实现按需加载功能
limit
小于比图片大,那么图片将被转化为base64
编码格式/* webpack.prod.js */ // 处理URL路径的loader { test: /\.(jpg|png|gif|bmp|jpeg)$/, use: { loader: 'url-loader', options: { limit: 8192, name: 'images/[hash:7].[ext]' // 做用:将图片输出到images文件夹中,文件名采用7位的哈希值(MD5),而且保持原来的图片文件扩展名 // name:指定文件输出路径和输出文件命令规则 // [hash:7]:表示使用7位哈希值表明文件名称 // [ext]:表示保持文件原有后缀名 // name: 'imgs/img-[hash:7].[ext]' } } },
npm i -D clean-webpack-plugin
/* webpack.prod.js */ const cleanWebpackPlugin = require('clean-webpack-plugin') plugins: [ // 建立一个删除文件夹的插件,删除dist目录 new cleanWebpackPlugin(['./dist']) ]
目的:将公共的第三方包,抽离为一个单独的包文件,这样防止重复打包!
/* webpack.prod.js */ // 1 入口 -- 打包文件的入口 entry: { // 项目代码入口 app: path.join(__dirname, './src/js/main.js'), // 第三方包入口 vendor: ['vue', 'vue-router', 'axios'] }, output: { // 2 修改输出文件路径和命名规则 filename: 'js/[name].[chunkhash].js', }, plugins: [ // 3 抽离第三方包 new webpack.optimize.CommonsChunkPlugin({ // 将 entry 中指定的 ['vue', 'vue-router', 'axios'] 打包到名为 vendor 的js文件中 // 第三方包入口名称,对应 entry 中的 vendor 属性 name: 'vendor', }), ]
plugins: [ // 优化代码 // https://github.com/webpack-contrib/uglifyjs-webpack-plugin/tree/v0.4.6 new webpack.optimize.UglifyJsPlugin({ // 压缩 compress: { // 移除警告 warnings: false } }), // 指定环境为生产环境:vue会根据这一项启用压缩后的vue文件 new webpack.DefinePlugin({ 'process.env': { 'NODE_ENV': JSON.stringify('production') } }) ]
npm i -D extract-text-webpack-plugin
npm i -D optimize-css-assets-webpack-plugin
压缩和抽离CSS报错的说明: Error processing file: css/style.css postcss-svgo: Error in parsing SVG: Unquoted attribute value 缘由:压缩和抽离CSS的插件中只容许 SVG 使用双引号
/* webpack.prod.js */ // 分离 css 到独立的文件中 const ExtractTextPlugin = require("extract-text-webpack-plugin"); // 压缩 css 资源文件 const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin') // bug描述: 生成后面的css文件中图片路径错误,打开页面找不到图片 // 解决:google搜索 webpack css loader 样式图片路径 output: { // ... // https://doc.webpack-china.org/configuration/output/#output-publicpath // 设置公共路径 publicPath: '/', }, module: { rules: [ { test: /\.css$/, use: ExtractTextPlugin.extract({ fallback: "style-loader", use: "css-loader" }) }, { test: /\.scss$/, use: ExtractTextPlugin.extract({ fallback: "style-loader", use: ['css-loader', 'sass-loader'] }) }, ] }, plugins: [ // 经过插件抽离 css (参数) new ExtractTextPlugin("css/style.css"), // 抽离css 的辅助压缩插件 new OptimizeCssAssetsPlugin() ]
new htmlWebpackPlugin({ // 模板页面 template: path.join(__dirname, './index.html'), // 压缩HTML minify: { // 移除空白 collapseWhitespace: true, // 移除注释 removeComments: true, // 移除属性中的双引号 removeAttributeQuotes: true } }),
// 方式一: require.ensure() const NewsList = r => require.ensure([], () => r(require('../components/news/newslist.vue')), 'news') // 方式二: import() -- 推荐 // 注意:/* webpackChunkName: "newsinfo" */ 是一个特殊的语法,表示生成js文件的名称 const NewsInfo = () => import(/* webpackChunkName: "newsinfo" */ '../components/news/newsinfo.vue')
output: { // ------添加 chunkFilename, 指定输出js文件的名称------ chunkFilename: 'js/[name].[chunkhash].js', },