之前,我有提到过,当然大家肯定也都知道,Vue3.0不在有webpack.config.js的配置;但是不可避免,在项目开发中我们肯定会存在一些特殊的需求需要调整webpack, 这个时候,在Vue3.0的项目当中,我们就需要在根目录创建vue.config.js去完成webpack的一些特殊配置,默认它会被 @vue/cli-service 自动加载。
此刻,你需要创建vue.config.js文件。
Vue CLI 官方文档:vue-cli-service 暴露了 inspect 命令用于审查解析好的 webpack 配置。那个全局的 vue 可执行程序同样提供了 inspect 命令,这个命令只是简单的把 vue-cli-service``inspect 代理到了你的项目中。
被抽象化的webpack,我们要想去理解它默认的一些配置的话是比较困难的,所以我们可以通过指令去查看。
该指令会将webpack的配置输出到output.js文件,这样方便去查看。
vue inspect > output.js
复制代码
复制代码
这个文件导出了一个包含了选项的对象:
module.exports={
// 选项...
}
复制代码
复制代码
接下来,详细介绍一些选项及配置:
module.exports={
productionSourceMap: false,
publicPath: './',
outputDir: 'dist',
assetsDir: 'assets',
devServer: {
port: 8090,
host: '0.0.0.0',
https: false,
open: true
},
// 其他配置
...
复制代码
复制代码
在vue.config.js如果要新增/修改 webpack 的 plugins 或者 rules , 有2种方式。
configureWebpack 是相对比较简单的一种方式
configureWebpack: {
rules:[],
plugins: []
}
configureWebpack: (config)=> {
// 例如,通过判断运行环境,设置mode
config.mode='production'
}
复制代码
复制代码
chainWebpack 链式操作 (高级),接下来所有的配置我都会在该选项中进行配置
关于rules 的配置,我会分别从新增/修改进行介绍。
在webpack中 rules 是 module 的配置项,而所有的配置的都是挂载到 config 下的,所以新增一个rule方式:
config.module
.rule(name)
.use(name)
.loader(loader)
.options(options)
复制代码
复制代码
module.exports={
chainWebpack: (config)=> {
// 通过 style-resources-loader 来添加less全局变量
const types=['vue-modules', 'vue', 'normal-modules', 'normal'];
types.forEach(type=> {
let rule=config.module.rule('less').oneOf(type)
rule.use('style-resource')
.loader('style-resources-loader')
.options({
patterns: [path.resolve(__dirname, './lessVariates.less')]
});
});
// `svg-sprite-loader`: 将svg图片以雪碧图的方式在项目中加载
config.module
.rule('svg')
.test(/.svg$/) // 匹配svg文件
.include.add(resolve('src/svg')) // 主要匹配src/svg
.end()
.use('svg-sprite-loader')
.loader('svg-sprite-loader') // 使用的loader,主要要npm该插件
.options({symbolId: 'svg-[name]'}) // 参数配置
}
}
复制代码
复制代码
针对已经存在的 rule , 如果需要修改它的参数, 可以使用 tap 方法:
config.module
.rule(name)
.use(name)
.tap(options=> newOptions)
复制代码
复制代码
module.exports={
chainWebpack: (config)=> {
// `url-loader`是webpack默认已经配置的,现在我们来修改它的参数
config.module.rule('images')
.use('url-loader')
.tap(options=> ({
name: './assets/images/[name].[ext]',
quality: 85,
limit: 0,
esModule: false,
}))
}
}
复制代码
复制代码
关于 plugins 的配置,我会分别从新增/修改/删除进行介绍。
config
.plugin(name)
.use(WebpackPlugin, args)
复制代码
复制代码
const HotHashWebpackPlugin=require('hot-hash-webpack-plugin');
module.exports={
chainWebpack: (config)=> {
// 新增一个`hot-hash-webpack-plugin`
// 注意:这里use的时候不需要使用`new HotHashWebpackPlugin()`
config.plugin('hotHash')
.use(HotHashWebpackPlugin, [{ version: '1.0.0' }]);
}
}
复制代码
复制代码
同理, plugin 参数的修改也是通过 tap 去修改。
config
.plugin(name)
.tap(args=> newArgs)
复制代码
复制代码
const HotHashWebpackPlugin=require('hot-hash-webpack-plugin');
module.exports={
chainWebpack: (config)=> {
// 修改打包时css抽离后的filename及抽离所属目录
config.plugin('extract-css')
.tap(args=> [{
filename: 'css/[name].[contenthash:8].css',
chunkFilename: 'css/[name].[contenthash:8].css'
}]);
// 正式环境下,删除console和debugger
config.optimization
.minimize(true)
.minimizer('terser')
.tap(args=> {
let { terserOptions }=args[0];
terserOptions.compress.drop_console=true;
terserOptions.compress.drop_debugger=true;
return args
});
}
}
复制代码
复制代码
对于一些webpack默认的 plugin ,如果不需要可以进行删除
config.plugins.delete(name)
复制代码
复制代码
module.exports={
chainWebpack: (config)=> {
// vue-cli3.X 会自动进行模块分割抽离,如果不需要进行分割,可以手动删除
config.optimization.delete('splitChunks');
}
}
复制代码
复制代码
webpack 默认的 entry 入口是 scr/main.ts
config.entryPoints.clear(); // 清空默认入口
config.entry('test').add(getPath('./test/main.ts')); // 重新设置
复制代码
复制代码
定义全局全局变量,DefinePlugin 是 webpack 已经默认配置的,我们可以对参数进行修改
config.plugin('define').tap(args=> [{
...args,
"window.isDefine": JSON.stringify(true),
}]);
复制代码
复制代码
自定义打包后js文件的路径及文件名字
config.output.filename('./js/[name].[chunkhash:8].js');
config.output.chunkFilename('./js/[name].[chunkhash:8].js');
复制代码
复制代码
html-webpack-plugin 是 webpack 已经默认配置的,默认的源模版文件是 public/index.html ;我们可以对其参数进行修改
config.plugin('html')
.tap(options=> [{
template: '../../index.html' // 修改源模版文件
title: 'test',
}]);
复制代码
复制代码
webpack默认是将src的别名设置为@, 此外,我们可以进行添加
config.resolve.alias
.set('@', resolve('src'))
.set('api', resolve('src/apis'))
.set('common', resolve('src/common'))
复制代码
复制代码
、MVVM简介
如果你是第一次学前端,那么本节知识一定要了解,什么是MVVM。
MVVM是Model-View-ViewModel的简写。它本质上就是MVC 的改进版。MVVM 就是将其中的View 的状态和行为抽象化,让我们将视图 UI 和业务逻辑分开。当然这些事 ViewModel 已经帮我们做了,它可以取出 Model 的数据同时帮忙处理 View 中由于需要展示内容而涉及的业务逻辑。MVVM的核心是ViewModel层,负责转换Model中的数据对象来让数据变得更容易管理和使用。是一种简化用户界面的事件驱动编程方式。
下边我们来画张图来大体了解下MVVM的工作原理图:
该层向上与视图层进行双向数据绑定
向下与Model层通过接口请求进行数据交互
(1)View
View是视图层, 也就是用户界面。前端主要由HTH L和csS来构建, 为了更方便地展现vi eu to del或者Hodel层的数据, 已经产生了各种各样的前后端模板语言, 比如FreeMarker,Thyme leaf等等, 各大MV VM框架如Vue.js.Angular JS, EJS等也都有自己用来构建用户界面的内置模板语言。
(2)Model
Model是指数据模型, 泛指后端进行的各种业务逻辑处理和数据操控, 主要围绕数据库系统展开。这里的难点主要在于需要和前端约定统一的接口规则
(3)ViewModel
ViewModel是由前端开发人员组织生成和维护的视图数据层。在这一层, 前端开发者对从后端获取的Model数据进行转换处理, 做二次封装, 以生成符合View层使用预期的视图数据模型。
View Model所封装出来的数据模型包括视图的状态和行为两部分, 而Model层的数据模型是只包含状态的
视图状态和行为都封装在了View Model里。这样的封装使得View Model可以完整地去描述View层。由于实现了双向绑定, View Model的内容会实时展现在View层, 这是激动人心的, 因为前端开发者再也不必低效又麻烦地通过操纵DOM去更新视图。 MVVM框架已经把最脏最累的一块做好了, 我们开发者只需要处理和维护View Model, 更新数据视图就会自动得到相应更新,真正实现事件驱动编程。 View层展现的不是Model层的数据, 而是ViewModel的数据, 由ViewModel负责与Model层交互, 这就完全解耦了View层和Model层, 这个解耦是至关重要的, 它是前后端分离方案实施的重要一环。
2、为什么要使用MVVM
MVVM模式和MVC模式一样,主要目的是分离视图(View)和模型(Model),有几大优点
(1) 低耦合。视图(View)可以独立于Model变化和修改,一个ViewModel可以绑定到不同的"View"上,当View变化的时候Model可以不变,当Model变化的时候View也可以不变。
(2) 可重用性。你可以把一些视图逻辑放在一个ViewModel里面,让很多view重用这段视图逻辑。
(3)独立开发。开发人员可以专注于业务逻辑和数据的开发(ViewModel),设计人员可以专注于页面设计,使用Expression Blend可以很容易设计界面并生成xaml代码。
(4)可测试。界面素来是比较难于测试的,测试可以针对ViewModel来写
3、VUE概述
(1)什么是vue?
Vue是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合
这是官网给出的介绍,可能不是那么容易理解。简单来说,Vue是一个视图层框架,帮助我们更好的构建应用。
使用Vue和原生JS一个最显著的差别就是,Vue不再对DOM直接进行操作,而是通过对数据的操作,来改变页面。使用Vue构建的页面,是有一个个的组件组成的,当组件中定义的数据发生变化时,组件的显示也会跟着变化,且此过程无需刷新页面。
(2)MVVM模式的实现者
Model:模型层, 在这里表示JavaScript对象 View:视图层, 在这里表示DOM(HTML操作的元素) ViewModel:连接视图和数据的中间件, Vue.js就是MVVM中的View Model层的实现者 在MVVM架构中, 是不允许数据和视图直接通信的, 只能通过ViewModel来通信, 而View Model就是定义了一个Observer观察者
ViewModel能够观察到数据的变化, 并对视图对应的内容进行更新 ViewModel能够监听到视图的变化, 并能够通知数据发生改变 至此, 我们就明白了, Vue.js就是一个MV VM的实现者, 他的核心就是实现了DOM监听与数据绑定
(3)为什么要使用Vue
易用:熟悉HTML、CSS、JavaScript之后,可快速度上手vue。学习曲线平稳。
轻量级:Vue.js压缩后有只有20多kb,超快虚拟DOM
高效:吸取了Angular(模块化) 和React(虚拟DOM) 的优势, 并拥有自己独特的功能
开源:文档齐全,社区活跃度高
4、VUE之Hello World!
步骤一:创建空文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
</body>
</html>
步骤二:引入vue.js (本人下载的开发版的vue.js,跟本html文件放在了同一目录下,所以直接引用)
<script type="text/javascript" src="vue.js"></script>
步骤三:创建vue实例
<script type="text/javascript">
var vm=new Vue({
el:'#app',
data:{
msg:'Hello World'
}
});
</script>
步骤四:数据与页面元素绑定
<div id="app">
{{msg}}
</div>
完整的html
<!DOCTYPE html>
<html lang="en">
<body>
<div id="app">
{{msg}}
</div>
<script type="text/javascript" src="vue.js"></script>
<script type="text/javascript">
var vm=new Vue({
el:'#app',
data:{
msg:'Hello World'
}
});
</script>
</body>
</html>
浏览器打开:
参数分析:
el : '#app' -- 绑定元素的ID(元素的挂载位置,值可以是CSS选择器或者是DOM元素)
data : { msg : 'Hello World' } -- 模型数据,属性名:msg 值:Hello World
{{msg}} : 在绑定的元素中使用{{ }}将Vue创建的名为msg的属性包起来, 即可实现数据绑定功能,我们在调试状态下手动修改下msg的值,在不刷新页面的情况下就会展示我们修改后的值,这就是借助了Vue的数据绑定功能实现的。 MV VM模式中要求View Model层就是使用观察者模式来实现数据的监听与绑定, 以做到数据与视图的快速响应
下一篇:VUE入门教程(二)之模板语法(指令)
信小程序开发过程中,许多开发者会遇到 小程序 与 Web 端一起的需求,由于 小程序 与 Web 端的运行环境不同,开发者往往需要维护两套类似的代码,这对开发者来说比较耗费力气,并且会出现不同步的情况。
小程序作为web的配套必需品,已经成为开发者不可避免的工作。多终端、多形态的开发无疑徒增工作量,费力不讨好。Kbone 就是一个致力于微信小程序和 Web 端同构的解决方案。一套代码同时开发完微信小程序以及web端。
基本结构
首先,我们来看下一个基本的 kbone 项目的目录结构(这里的 todo 是基于 Vue 的示例, kbone 也有 React , Preact , Omi 等版本,详情可移步 kbone github )。
因为 kbone 是为了解决 小程序 与 Web 端的问题,所以每个目录下的配置都会有两份(小程序 与 Web 端各一份)
入口
不管是 小程序 端还是 Web 端,都需要入口文件。在 src/index 目录下, main.js 为 Web 端用主入口, main.mp.js 则为 小程序 端用主入口。
当然,Web 端会比 小程序 多一个入口页面,即 index.html (位于根目录下)。
下面两段代码分别是 小程序端 入口与 Web 端入口的代码,可以看到 小程序端的入口代码封装在 createApp 函数里面(这里固定即可),内部会比 Web 端多一个创建 app 节点的操作,其他的基本就是一致的。
todo.vue
在上面的入口图可以看到,源码目录中,除了入口文件分开之前,页面文件就是共用的了,这里直接使用 Vue 的写法即可,不用做特殊的适应。
配置
写完代码之后,我们要怎么跑项目呢?这时,配置就派上用场啦。
Web 端配置为正常的 Vue 配置,小程序端配置与 Web 端配置的唯一不同就是需要引入 mp-webpack-plugin 插件来将 Vue 组件转化为小程序代码。
构建代码
接着,我们需要构建代码,让代码可以运行到各自的运行环境中去。构建完成后,生产代码会位于 dist 目录中。
小程序端 的构建会比 Web 端的构建多一个步骤,就是 npm 构建。
进入 dist/mp 目录,执行 npm install 安装依赖,用开发者工具将 dist/mp 目录作为小程序项目导入之后,点击工具栏下的 构建 npm ,即可预览效果。
效果
最后,我们来看一下 todo 的效果。kbone 初体验,done~
todo 代码可到 kbone/demo13 自提。
具体方案实现
接下来就来探讨下具体方案的实现。
社区 Web 端是基于 Vue 实现的,使用了 Vue-router、Vuex 等插件。Vue 想必大家挺熟悉的了,它是市面上一款非常流行的 Web 框架,提供组件化等特性,其原理大致如下:
Vue 模板可以认为是一种附加了一些特殊语法的 HTML 片段,一般来说一份 Vue 模板对应一个组件,在构建阶段编译成调用 Dom 接口的 JS 函数,调用此 JS 函数就会创建出组件对应的 Dom 树片段进而渲染到浏览器上。小程序里是支持运行 JS 的,但是这里用到的 Dom 接口和渲染到浏览器上的功能小程序不具备,所以无法直接将 Web 端社区代码移植到小程序中。原因就在于小程序为了安全和性能而采用了双线程的架构,运行用户 JS 代码的逻辑层是一个纯粹的 JSCore,没有任何浏览器相关的实现,这里得想办法将 Web 端代码转成小程序代码。
业界常见做法:将 Vue 模板直接转成小程序的 WXML 模板
那么问题来了,如何将 Vue 代码转成小程序代码?这里先看下业界常见的做法:将 Vue 模板直接转成小程序的 WXML 模板。
使用做法相当于抛弃了浏览器中建 Dom 树的过程,而是直接交由小程序来对模板进行编译创建出小程序的模板树,进而渲染到小程序页面中。
一般来说这个做法对于普通场景是够用的,但是对于一些更复杂的场景就很不好处理了,比如社区中的一个简单例子:社区帖子详情展示富文本内容,点击内容中的图片可预览。
这主要是因为 Vue 模板和 WXML 模板的语法并不是直接对等的,Vue 的特性设计也和小程序的设计无法划等号,这自然就导致了部分 Vue 特性的丢失。比如像 Vue 中的 v-html 指令、ref 获取 Dom 节点、过滤器等就通通用不了。当然不止是 Vue 自身的特性,一些原本依赖 Dom/Bom 接口的 Vue 插件也无法使用,比如 Vue-router 等,而这些正是社区高度依赖的,在不对社区代码做大范围改造的话是无法使用此方案的。
此路不通,那还有其他的方法么?
换个思路:做一个适配层
答案是有的,这里我们就得换一种思路来解决这个问题。回到最初的点上,我们无法将 Web 端代码移植到小程序中是因为小程序没有 Dom 接口,那么我们想办法做出一个适配层,将这个差异给抹掉不就行了么?
有了想法就要实施,仿造出 Dom 接口并不难,事实上在 Nodejs 端就有人做过类似的事,比如 jsDom 这个库的实现,让我们可以在没有真实浏览器环境下可以对一些依赖 Dom 接口的 Web 端代码进行测试。
仿造了 Dom 接口给 Vue 调用,进而创建出了仿造 Dom 树。根据前面提到的小程序架构,用户的 JS 代码是执行在逻辑层的,也就是说我们创建出的 Dom 树也是存在与逻辑层的内存之中,接下来要解决的难题是如何将这棵 Dom 树渲染到小程序页面中。
这里需要先简单介绍一下小程序的渲染原理:小程序的双线程架构,逻辑层会执行用户的 JS 代码进而产生一组数据,这组数据会发往视图层;视图层接收到数据后,结合用户的 WXML 模板创建出组件树,之后小程序再将组件树渲染出来。这里的组件树和 Dom 树很类似,只是它是由官方内置组件或自定义组件拼接而成而不是 Dom 节点。这里我们能不能将仿造出来的 Dom 树映射到小程序的组件树上?
小程序组件树是根据 WXML 模板创建出来的,而仿造 Dom 树结构是不稳定的,我们无法提前预知它会生成什么样的结构,也就无法提前准备后可以描述任意 Dom 树的 WXML 模板,除非直接将 Vue 模板转换成 WXML 模板,但这样又绕回前面的问题上了。
小程序组件树中的组件有两种:内置组件和自定义组件,内置组件是由官方提供的如 video、map 这样的组件,而自定义组件是一种支持由用户利用现有组件自行组装的组件,能否利用它来做些什么?
使用 Web 端概念来做个简单解释,内置组件就像是 div、span 这些 HTML 标签,而自定义组件就像是 Web 中的 Vue 组件。Vue 组件可以将 HTML 标签以及其他的 Vue 组件进行组装,自定义组件同理,主要用于功能模块的抽象、封装和复用。不过自定义组件有个很奇妙的特性,它支持自引用,也就是说它可以自己引用自己来进行组装。
自定义组件可以自己引用自己,那么我们就可以利用这个特性来进行递归创建组件,进而创建出一棵组件树:
比如上图的例子,我们封装了一个 custom-dom 组件,这个组件里面也使用了 custom-dom 组件用于渲染子组件。那么只要我们执行一下 setData,把 children 数据传递过去就可以创建出子组件,子组件本身也是 custom-dom 组件,它同样可以执行这个逻辑把各自的子组件创建出来,这样就实现了组件的递归创建,只要我们拥有完整的 Dom 树结构,就可以创建出相对应的一棵组件树。
这里递归的终止条件是遇到特定节点、文本节点或者孩子节点为空。然后在创建出组件树后,将 Dom 节点和自定义组件实例进行绑定以便后续的 Dom 更新和操作即可。
我们将其归纳为两个模块:仿造接口和自定义组件。正因为这个方案是通过提供适配器的方式来仿造出 Web 环境,所以用户代码不需要做任何魔改,大部分特性都可以继续使用不需要被删减,比如 vue-router、window.location 操作等。
原本 Web 端代码是基于 Vue 来搭建的,其中还用到了诸多插件/库,如 Vue-router、Vuex、Markdown-it 等,同时还支持了服务端渲染。但是不管 Web 端是怎么实现的,底层终究是调浏览器的那些接口,所以对于用户层面的代码我们不做任何调整,只是将浏览器那一层替换掉即可。
整个构建流程是基于 Webpack 来实现的,使用 Kbone 构建出小程序代码也是基于 Webpack 来实现,只需要在原本 Web 端构建流程上实现一个 Webpack 插件,在构建原本 Web 端代码到小程序端时追加 Kbone 和一些小程序相关的代码即可
*请认真填写需求信息,我们会在24小时内与您取得联系。