小程序开发.mpvue.项目构建与运行
本文介绍 mpvue 框架的特点以及相关生态,包括在 mpvue 中使用 typescript 和 css 预处理器的相关方法。最后介绍一个最基本 mpvue 项目的创建。
Tips:
- 阅读本文前,请参考博文 《小程序开发.概述与环境搭建》(blog.csdn.net/qq_28550263…) 以完全开发前的相关准备工作。
jcLee95 的个人博客
邮箱 :291148484@163.com
CSDN 主页:blog.csdn.net/qq_28550263…
本文地址:blog.csdn.net/qq_28550263…
目 录
1. mpvue 的主要特性
使用 mpvue 开发小程序,你将获得近乎完整的 Vue.js 开发体验。你可以使用 Vue.js 命令行工具 vue-cli
快速初始化项目(见7.1 初始化(创建)mpvue项目小节)。
在 mpvue 项目中,你可以使用 npm 外部依赖。你也将获得 vue 一样的彻底的组件化开发能力。
使用 mpvue 开发小程序,将采用快捷的 webpack 构建机制,你可以自定义构建策略、开发阶段的热重载。
mpvue-loader 基于 vueloader 的基础上做了一些列基于小程序项目的改造,你可以用熟悉的传统的 vue 的开发方式进行代码的书写,比如 vue 模板部分你可以使用 html5 的写法。这得益于H5 代码转换编译成小程序目标代码的能力。也就是说 mpvue-loader 将为我们将 html5 转换为 wxml。
在你的 mpvue 项目中甚至还可以使用状态管理组件 Vuex 提供的数据管理方案,这显然有利于构建复杂应用。
2. mpvue 项目结构
2.1 概述
阅读本节前,你可以根据本文的最后一节 “第一个 mpvue 项目”中的步骤自己搭建第一个 mpvue项目。下图是一个新创建的 mpvue 项目,部分目录已经展开:
yourproj
├─[build] # 用于存放
│ ├─ build.js
│ ├─ check-versions.js
│ ├─ dev-client.js
│ ├─ dev-server.js
│ ├─ utils.js
│ ├─ vue-loader.conf.js
│ ├─ webpack.base.conf.js
│ ├─ webpack.dev.conf.js
│ ├─ webpack.prod.conf.js
├─[config] # 用于存放项目相关的配置文件
│ ├─ dev.env.js
│ ├─ index.js
│ └─ prod.env.js
├─[dist] # 用于存放构建后的原生小程序代码
├─[node_modules] # 相关依赖模块
├─[src] # 项目资源主目录
│ ├─[components] # 存放 vue 组件
│ ├─[pages] # 用于存放页面的目录
│ │ ├─[index] # 这是主页,其它页面目录结构也一样
│ │ │ ├─ index.vue # 该页面的视图组件
│ │ │ ├─ main.js # 一般用于创建vue实例(vue2)
│ │ │ └─ store.js # 该页面的 vuex 存储文件
│ │ └─ ...
│ ├─[utils] # 存放工具脚本
│ ├─ app.json # 用于描述小程序的整体逻辑结构
│ ├─ app.js # 用于描述小程序的整体逻辑
│ ├─ App.vue # 项目的vue根组件
│ └─ main.js # 项目入口js
└─[static] # 存放你的静态文件,如图片等
├─ .babelrc
├─ .editorconfig
├─ .eslintrc.js
├─ .gitignore
├─ .postcssrc.js
├─ index.html
├─ package.json # Node 项目的主配置文件
├─ package.swan.json
├─ project.config.json # 开发者工具上做的任何配置都会写入该文件,当重新安装工具或者换计算机工作时,只要载入同一个项目的代码包,开发者工具就会自动恢复相关个性化配置
└─ README.md
2.2 全局配置文件
包括 app.js
(或app.ts
)、main.js
(或main.ts
)、app.json
、page.js
、project.config.json
2.2.1 main.js 和 app.json
1. webpack entry 与 main.js
(1)main.js 是什么
全局main.js
用作 webpack 打包的入口,这由 webpack 的配置中的 entry
字段指定的内容开始,因此如果你不喜欢也可以自己指定它的名字和位置。
// webpack.config.js
{
// ...
entry: {
app: resolve('./src/main.js'), // app 字段被识别为 app 类型
index: resolve('./src/pages/index/main.js'), // 其余字段被识别为 page 类型
'news/home': resolve('./src/pages/news/home/index.js')
}
}
一个最简单的mian.ts
看起来是这样:
import Vue from 'vue'
import App from './App'
Vue.config.productionTip = false
App.mpType = 'app'
const app = new Vue(App)
app.$mount()
或者使用 typescript,你需要使用vue-property-decorator
:
import { Component, Emit, Inject, Model, Prop, Provide, Vue, Watch } from 'vue-property-decorator';
import { VueConstructor } from "vue";
let isApp = false; //尝试mpvue-entry
let MyApp;
/* app-only-begin */
isApp = true;
interface IMpVue extends VueConstructor {
mpType: string
}
// 添加小程序hooks http://mpvue.com/mpvue/#_4
Component.registerHooks([
// app
'onLaunch', // 初始化
'onShow', // 当小程序启动,或从后台进入前台显示
'onHide', // 当小程序从前台进入后台
// pages
'onLoad', // 监听页面加载
'onShow', // 监听页面显示
'onReady', // 监听页面初次渲染完成
'onHide', // 监听页面隐藏
'onUnload', // 监听页面卸载
'onPullDownRefresh', // 监听用户下拉动作
'onReachBottom', // 页面上拉触底事件的处理函数
'onShareAppMessage', // 用户点击右上角分享
'onPageScroll', // 页面滚动
'onTabItemTap', //当前是 tab 页时, 点击 tab 时触发 (mpvue 0.0.16 支持)
])
Vue.config.productionTip = false
// add this to handle exception
Vue.config.errorHandler = function (err) {
if (console && console.error) {
console.error(err)
}
}
/* app-only-end */
if (isApp) {
// 在这个地方引入是为了registerHooks先执行
MyApp = require('./App.vue').default as IMpVue
}else {
// MyApp = require('./index.vue')
}
const app = new Vue(MyApp)
app.$mount()
(2)全局 main.js 与页面 main.js
我们实际上小程序的每一个页面都需要指定一个entry,这和常规的 vue开发不一样。因此,在 build\webpack.base.conf.js
中,往往是这样配置的:
// build\webpack.base.conf.js
const appEntry = { app: resolve('./src/main.js') }; // 用作全局(app) entry的 main.js
const pagesEntry = getEntry(resolve('./src'), 'pages/**/main.js'); // 用作页面(page) entry的 main.js
const entry = Object.assign({}, appEntry, pagesEntry);
// ...
也就是说,在项目 src
目录下的main.js
用作全局 entry 的 main.js ,而在所有pages/页面名/
目录下的main.js,用作页面(page) entry
。一个页面的 mian.js
看起来是这样:
// src/pages/**/main.js
import Vue from 'vue'
import App from './index.vue'
// add this to handle exception
Vue.config.errorHandler = function (err) {
if (console && console.error) {
console.error(err)
}
}
const app = new Vue(App)
app.$mount()
从上面可以看出,app.vue
通过webpack在以全局 main.js
作为路口,构建生成小程序(如微信小程序)所需要的全局app.js
、app.json
和 app.wxss
,其示意图如下:
1. app.vue & main.js
┌---------┐ ┌----------┐ ┌---------┐
| app.vue ├---┐ | app.js | | app.json|
└---------┘ | └----------┘ └---------┘
├---------------------->
┌---------┐ | webpack ┌----------┐
| main.js ├---┘ | app.wxss |
└---------┘ └----------┘
而对于某一个页面(xxpage),它是一个单独的 vue 单页面应用,webpack 将以其main.js(或main.ts)作为入口(在webpack.base.config.js配置,如上文所说),进行构建。构建完成后,将生成该页面的(以微信小程序为例).js
、.json
、.wxml
以及 .wxss
文件。如下图所示意:
2. xxpage.vue & main.js
┌--------------------------------┐
┌-----┴-------┐ ┌-------┐ |
| xxpage.vue ├----> | .sass | |
└---┬-┬-┬-----┘ └-------┘ |
| | | |
| | | ┌------------┐ |
| | └---> | common.css | | ┌-------┐ ┌-------┐
| | └-------┬----┘ v | .js | | .json |
| | | ┌---------┐ └-------┘ └-------┘
| | ┌------┐ | | main.js ├------------->
| └-->| .js | v └---------┘ webpack ┌-------┐ ┌-------┐
| └------┘ ┌-------┐ | .wxml | | .wxss |
| ┌------┐ | .font | └-------┘ └-------┘
└---->| .jpg | └-------┘
└------┘
2.2.1.2 app.json
app.json
中的内容来源于 app 和 page 的 entry 文件,通常习惯是 main.js
,你需要在你的入口文件中 export default { config: {} }
,这才能被我们的 loader 识别为这是一个配置,需要写成 json 文件。
{
"pages": [
"pages/index/main",
"pages/logs/main",
"pages/counter/main",
"pages/page2/main",
"pages/testExtend/main"
],
"subPackages": [
{
"root": "packageA",
"pages": [
"pages/index/main"
]
}
],
"window": {
"backgroundTextStyle": "light",
"navigationBarBackgroundColor": "#fff",
"navigationBarTitleText": "WeChat",
"navigationBarTextStyle": "black"
}
}
2.2.2 app.js
该文件时小程序的全局逻辑文件,主要用于定义小程序的 全局数据 和 全局函数。如:
import Vue from 'vue'
import App from './App'
Vue.config.productionTip = false
App.mpType = 'app'
const app = new Vue(App)
app.$mount()
使用 typescript 的时候,它被替换成为了app.ts
,内容大致如:
import { Vue, Component } from 'vue-property-decorator'
import Api from '@/data/api'
const debug = require('debug')('log:App')
declare module "vue/types/vue" {
interface Vue {
$mp: any;
}
}
// 必须使用装饰器的方式来指定components
@Component({
mpType: 'app', // mpvue特定
} as any)
class App extends Vue {
// app hook
onLaunch() {
let opt = this.$root.$mp.appOptions
debug('onLaunch', opt)
Api.login().then(res => {
debug('login', res)
})
}
onShow() {
debug('onShow')
}
onHide() {
debug('onHide')
}
mounted() { // vue hook
debug('mounted')
}
}
export default App
2.2.3 App.vue
这是mpvue项目的vue根组件。看起来大概是这样的:
<script>
export default {
created () {
// 调用API从本地缓存中获取数据
/*
* 平台 api 差异的处理方式: api 方法统一挂载到 mpvue 名称空间, 平台判断通过 mpvuePlatform 特征字符串
* 微信:mpvue === wx, mpvuePlatform === 'wx'
* 头条:mpvue === tt, mpvuePlatform === 'tt'
* 百度:mpvue === swan, mpvuePlatform === 'swan'
* 支付宝(蚂蚁):mpvue === my, mpvuePlatform === 'my'
*/
let logs
if (mpvuePlatform === 'my') {
logs = mpvue.getStorageSync({key: 'logs'}).data || []
logs.unshift(Date.now())
mpvue.setStorageSync({
key: 'logs',
data: logs
})
} else {
logs = mpvue.getStorageSync('logs') || []
logs.unshift(Date.now())
mpvue.setStorageSync('logs', logs)
}
},
log () {
console.log(`log at:${Date.now()}`)
}
}
</script>
<style>
.container {
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
padding: 200rpx 0;
box-sizing: border-box;
}
/* this rule will be remove */
* {
transition: width 2s;
-moz-transition: width 2s;
-webkit-transition: width 2s;
-o-transition: width 2s;
}
</style>
如果使用的是typescript,则<script>
标签需要指明所使用的语言,如:
<script lang="ts">
import { Vue, Component } from 'vue-property-decorator'
import Api from '@/data/api'
const debug = require('debug')('log:App')
declare module "vue/types/vue" {
interface Vue {
$mp: any;
}
}
// 必须使用装饰器的方式来指定components
@Component({
mpType: 'app', // mpvue特定
} as any)
class App extends Vue {
// app hook
onLaunch() {
let opt = this.$root.$mp.appOptions
debug('onLaunch', opt)
Api.login().then(res => {
debug('login', res)
})
}
onShow() {
debug('onShow')
}
onHide() {
debug('onHide')
}
mounted() { // vue hook
debug('mounted')
}
}
export default App
</script>
<style>
.container {
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
padding: 200px 0;
box-sizing: border-box;
}
/* this rule will be remove */
* {
transition: width 2s;
-moz-transition: width 2s;
-webkit-transition: width 2s;
-o-transition: width 2s;
}
</style>
当然也可以使用外部的脚本,只需添加 src
属性:
<script lang="ts" src="./app.ts"></script>
3. mpvue-loader
mpvue-loader 是在 vue-loader 基础上修改而来的。和 vue-loader 一样,mpvue-loader是一个 webpack 的 loader(预处理器,或称加载器),它允许你以一种名为 单文件组件 (SFCs) 的格式撰写 Vue 组件。
我们知道,wxml(WeiXin Markup Language)是微信小程序框架设计的一套标签语言,其他小程序也有类似的标签语言。我们以传统 vue 的方式写的时候,使用的不是wxml而是html,因此需要进行转换,生成符合 wxml 规范的模板。
有了 vue-loader, 每一个 .vue 的组件都会被生成为一个 WXML 规范的 template,然后通过 wxml 规范的 import 语法来达到一个复用,同时组件如果涉及到 props 的 data 数据,mpvue-loader 也会做相应的处理。
<template>
<div class="my-component">
<h1>{{msg}}</h1>
<other-component :msg="msg"></other-component>
</div>
</template>
<script>
import otherComponent from './otherComponent.vue'
export default {
components: { otherComponent },
data () {
return { msg: 'Hello Vue.js!' }
}
}
</script>
经过转换后,将会生成相应的 wxml:
<import src="components/other-component$hash.wxml" />
<template name="component$hash">
<view class="my-component">
<view class="_h1">{{msg}}</view>
<template is="other-component$hash" wx:if="{{ $c[0] }}" data="{{ ...$c[0] }}"></template>
</view>
</template>
在 webpack 中,所有的预处理器需要匹配对应的 loader。 vue-loader 允许你使用其它 webpack loader 处理 Vue 组件的某一部分,mpvue-loader 也是一样。它能够更具 lang
特性以及你 webpack 配置中的规则自动推断出要使用的 loader。
4. TypeScript 支持
mpvue-loader目前支持用TypeScript来写,功能还在完善中(WIP)。目前实现了用:
<script lang="ts" src="./xx.ts"></script>
这种方式的自动识别,并且需要搭配 vue-property-decorator 来使用。
首先你需要在你的项目中安装一些开发依赖:
npm install -D typescript awesome-typescript-loader vue-property-decorator
vue-property-decorator 完全依赖于 vue-class-component。vue-class-component 类风格的 Vue 组件的 ECMAScript / TypeScript 装饰器,它可让你以类样式语法制作 Vue 组件,例如:
<template>
<div>
<button v-on:click="decrement">-</button>
{{ count }}
<button v-on:click="increment">+</button>
</div>
</template>
<script>
import Vue from 'vue'
import Component from 'vue-class-component'
// 以类的方式定义组件
@Component
export default class Counter extends Vue {
// 类属性将是组件数据
count = 0
// 方法将是组件方法
increment() {
this.count++
}
decrement() {
this.count--
}
}
</script>
安装后,在 webpack.conf.js
中添加对应的 loader
:
// webpack.conf.js
module.exports = {
//...
module: {
rules: [
{
test: /\.vue$/,
loader: 'mpvue-loader',
options: {
//...
ts: [ //添加对应vue的loader
'babel-loader',
{
// loader: 'ts-loader',
loader: 'awesome-typescript-loader',
options: {
// errorsAsWarnings: true,
useCache: true,
}
}
]
}
},
// ts文件的loader
{
test: /\.tsx?$/,
exclude: /node_modules/,
use: [
'babel-loader',
{
loader: 'mpvue-loader',
options: {
checkMPEntry: true
}
},
{
// loader: 'ts-loader',
loader: 'awesome-typescript-loader',
options: {
// errorsAsWarnings: true,
useCache: true,
}
}
]
},
]
// ...
}
你的 main.js
对应改为 main.ts
,
import { Component, Emit, Inject, Model, Prop, Provide, Vue, Watch } from 'vue-property-decorator';
import { VueConstructor } from "vue";
interface IMpVue extends VueConstructor {
mpType: string
}
// 添加小程序hooks http://mpvue.com/mpvue/#_4
Component.registerHooks([
// app
'onLaunch', // 初始化
'onShow', // 当小程序启动,或从后台进入前台显示
'onHide', // 当小程序从前台进入后台
// pages
'onLoad', // 监听页面加载
'onShow', // 监听页面显示
'onReady', // 监听页面初次渲染完成
'onHide', // 监听页面隐藏
'onUnload', // 监听页面卸载
'onPullDownRefresh', // 监听用户下拉动作
'onReachBottom', // 页面上拉触底事件的处理函数
'onShareAppMessage', // 用户点击右上角分享
'onPageScroll', // 页面滚动
'onTabItemTap', // 当前是 tab 页时, 点击 tab 时触发 (mpvue 0.0.16 支持)
])
Vue.config.productionTip = false;
// 在这个地方引入是为了registerHooks先执行
const MyApp = require('./App.vue').default as IMpVue;
const app = new Vue(MyApp);
app.$mount();
将原 App.vue修改为 typescript 脚本:
<script lang="ts">
import { Vue, Component } from 'vue-property-decorator'
declare module "vue/types/vue" {
interface Vue {
$mp: any;
}
}
// 必须使用装饰器的方式来指定components
@Component({
mpType: 'app', // mpvue特定
}as any)
class App extends Vue {
// app hook
onLaunch() {
let opt = this.$root.$mp.appOptions
}
onShow(){
// ...
}
onHide(){
// ...
}
mounted(){
// vue hook
}
}
export default App
</script>
<style>
</style>
★ 注意:
- 你也可以将 typescript 脚本写到单独的文件中,比如:
<script lang="ts" src="./app.ts"></script>
- 如果你要想上面那样,直接在
.vue
文件中写ts,需要在vue-loader.conf.js
文件(build目录中):
{
loader: 'ts-loader',
options: {
// errorsAsWarnings: true,
appendTsSuffixTo: [/\.vue$/],
}
},
5. Sass 支持
yarn add node-sass sass-loader -D
更改 webpack.base.conf.js
(build目录中):
{
test: /\.sass$/,
loaders: ['style', 'css', 'sass']
}
在.vue
中修改style标签
<style lang="scss">
/* 你的样式 */
</style>
你也可以引入外部样式文件,例如:
<style lang="scss">
@import "static/app.scss";
/* 你的其它样式 */
</style>
6. less 支持
安装 less
和 less-loader
:
yarn add less less-loader -D
在 webpack.base.conf.js
的 module
的 rules
下增加规则:
{
test: /\.less$/,
exclude: /node_modules/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: [
{
loader: 'css-loader',
options: {
importLoaders: 1,
url: false,
},
},
'less-loader',
],
}),
},
7. 手把手创建一个 mpvue 项目
7.1 初始化(创建)mpvue项目
博文 《小程序开发.概述与环境搭建》(blog.csdn.net/qq_28550263…)中我们介绍了 mpvue 相关的环境准备,到这里你应该已经安装号了 vue-cli。现在我们使用 mpvue/cli 来初始化一个mpvue项目,项目名为 mpvuedemo
:
vue init mpvue/mpvue-quickstart mpvuedemo
以上选项可以根据你的需求来,也可以就使用默认值。创建完成后,进入项目,并安装依赖:
cd mpvuedemo
npm install
# 或者
yarn
7.2 添加 typescript 和 sass 支持
yarn add typescript awesome-typescript-loader vue-property-decorator -D
yarn add node-sass sass-loader -D
在项目的更目录下创建 typescript 配置文件 tsconfig.json
,添加以下内容(也可以根据你的需求配置):
// tsconfig.json
{
"compilerOptions": {
// 与 Vue 的浏览器支持保持一致
"target": "es2015",
// 这可以对 `this` 上的数据属性进行更严格的推断
"strict": true,
// 如果使用 webpack 2+ 或 rollup,可以利用 tree-shake:
"module": "es2015",
"moduleResolution": "node",
"baseUrl": "./",
"outDir": "./dist/",
"paths": {
"vue": [
"node_modules/mpvue"
],
"@/*": [
"src/*"
]
},
"types": [
"wechat-mp-types"
],
"allowJs": true,
"allowSyntheticDefaultImports": true,
"noImplicitAny": false,
"skipLibCheck": true,
"strictPropertyInitialization": false,
"experimentalDecorators": true
},
"include": [
"./src/**/*"
],
"exclude": [
"node_modules"
],
"typeAcquisition": {
"enable": true
}
}
接下来,由于我们想直接在 .vue
文件的一对<script></script>
标签中使用 typescript,修改 vue-loader.conf.js
文件:
// vue-loader.conf.js
var utils = require('./utils')
var config = require('../config')
// var isProduction = process.env.NODE_ENV === 'production'
// for mp
var isProduction = true
module.exports = {
loaders: Object.assign(utils.cssLoaders({
sourceMap: isProduction
? config.build.productionSourceMap
: config.dev.cssSourceMap,
extract: isProduction
}), {
ts: [
'babel-loader',
{
loader: 'awesome-typescript-loader',
options: {
// errorsAsWarnings: true,
useCache: true,
}
},
// 【添加 ts-loader】:*.vue文件直接写ts,使用的时候,注释掉上面的awesome-typescript-loader
{
loader: 'ts-loader',
options: {
// errorsAsWarnings: true,
appendTsSuffixTo: [/\.vue$/],
}
},
]
}),
transformToRequire: {
video: 'src',
source: 'src',
img: 'src',
image: 'xlink:href'
},
fileExt: config.build.fileExt
}
将你的项目 main.js
修改为 main.ts
:
// main.ts
import { Component, Emit, Inject, Model, Prop, Provide, Vue, Watch } from 'vue-property-decorator';
import { VueConstructor } from "vue";
let isApp = false; //尝试mpvue-entry
let MyApp;
/* app-only-begin */
isApp = true;
interface IMpVue extends VueConstructor {
mpType: string
}
// 添加小程序hooks http://mpvue.com/mpvue/#_4
Component.registerHooks([
// app
'onLaunch', // 初始化
'onShow', // 当小程序启动,或从后台进入前台显示
'onHide', // 当小程序从前台进入后台
// pages
'onLoad', // 监听页面加载
'onShow', // 监听页面显示
'onReady', // 监听页面初次渲染完成
'onHide', // 监听页面隐藏
'onUnload', // 监听页面卸载
'onPullDownRefresh', // 监听用户下拉动作
'onReachBottom', // 页面上拉触底事件的处理函数
'onShareAppMessage', // 用户点击右上角分享
'onPageScroll', // 页面滚动
'onTabItemTap', //当前是 tab 页时, 点击 tab 时触发 (mpvue 0.0.16 支持)
])
Vue.config.productionTip = false
// add this to handle exception
Vue.config.errorHandler = function (err) {
if (console && console.error) {
console.error(err)
}
}
/* app-only-end */
if (isApp) {
// 在这个地方引入是为了registerHooks先执行
MyApp = require('./App.vue').default as IMpVue
}else {
// MyApp = require('./index.vue')
}
const app = new Vue(MyApp)
app.$mount()
在你的项目更目录中建立vue-shim.d.ts
类型声明文件,并添加以下内容:
// vue-shim.d.ts
declare module "*.vue" {
import Vue from "vue";
export default Vue;
}
这对于TypeScript中,将所有的 .vue
安照一定规则作为一个 ts模块 进行处理,也就是.vue
模块的TypeScript类型声明。
接着就可以在你的 App.vue
中使用 typescript
语言了:
<script lang="ts">
import { Vue, Component } from 'vue-property-decorator'
import Api from '@/data/api'
const debug = require('debug')('log:App')
declare module "vue/types/vue" {
interface Vue {
$mp: any;
}
}
// 必须使用装饰器的方式来指定components
@Component({
mpType: 'app', // mpvue特定
} as any)
class App extends Vue {
// app hook
onLaunch() {
let opt = this.$root.$mp.appOptions
debug('onLaunch', opt)
Api.login().then(res => {
debug('login', res)
})
}
onShow() {
debug('onShow')
}
onHide() {
debug('onHide')
}
mounted() { // vue hook
debug('mounted')
}
}
export default App
</script>
<style>
.container {
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
padding: 200px 0;
box-sizing: border-box;
}
/* this rule will be remove */
* {
transition: width 2s;
-moz-transition: width 2s;
-webkit-transition: width 2s;
-o-transition: width 2s;
}
</style>
然后你可以继续更改你的 pages 页面目录下的 .vue 文件。
接着,为了使用 sass,在 webpack.base.conf.js
的 module.ruls
中增加 sass
的规则:
// webpack.base.conf.js
var path = require('path')
var fs = require('fs')
var utils = require('./utils')
var config = require('../config')
var webpack = require('webpack')
var merge = require('webpack-merge')
var vueLoaderConfig = require('./vue-loader.conf')
var MpvuePlugin = require('webpack-mpvue-asset-plugin')
var glob = require('glob')
var CopyWebpackPlugin = require('copy-webpack-plugin')
var relative = require('relative')
function resolve(dir) {
return path.join(__dirname, '..', dir)
}
function getEntry(rootSrc, gb = '/pages/**/main.ts') {
var map = {};
glob.sync(rootSrc + gb)
.forEach(file => {
var key = relative(rootSrc, file).replace('.ts', '');
map[key] = file;
})
return map;
}
const appEntry = { app: resolve('./src/main.ts') }
const pagesEntry = getEntry(resolve('./src'))
//分包A
const subpackagePagesEntry = getEntry(resolve('./src'), '/packageA/pages/**/main.ts')
const entry = Object.assign({}, appEntry, pagesEntry, subpackagePagesEntry)
let baseWebpackConfig = {
// 如果要自定义生成的 dist 目录里面的文件路径,
// 可以将 entry 写成 {'toPath': 'fromPath'} 的形式,
// toPath 为相对于 dist 的路径, 例:index/demo,则生成的文件地址为 dist/index/demo.js
entry,
target: require('mpvue-webpack-target'),
output: {
path: config.build.assetsRoot,
jsonpFunction: 'webpackJsonpMpvue',
filename: '[name].js',
publicPath: process.env.NODE_ENV === 'production'
? config.build.assetsPublicPath
: config.dev.assetsPublicPath
},
resolve: {
extensions: ['.js', '.vue', '.json', '.ts',],
alias: {
'vue': 'mpvue',
'@': resolve('src'),
'debug': resolve('src/utils/debug'),
'lodash': resolve('vendor/lodash_export'),
},
symlinks: false,
aliasFields: ['mpvue', 'weapp', 'browser'],
mainFields: ['browser', 'module', 'main']
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'mpvue-loader',
options: vueLoaderConfig
},
{
test: /\.tsx?$/,
// include: [resolve('src'), resolve('test')],
exclude: /node_modules/,
use: [
{
loader: 'babel-loader',
},
{
loader: 'mpvue-loader',
options: Object.assign({ checkMPEntry: true }, vueLoaderConfig)
},
{
// loader: 'ts-loader',
loader: 'awesome-typescript-loader',
options: {
// errorsAsWarnings: true,
useCache: true,
}
}
]
},
{
test: /\.less$/,
exclude: /node_modules/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: [
{
loader: 'css-loader',
options: {
importLoaders: 1,
url: false,
},
},
'less-loader',
],
}),
},
{
test: /\.js$/,
include: [resolve('src'), resolve('test')],
use: [
'babel-loader',
{
loader: 'mpvue-loader',
options: Object.assign({ checkMPEntry: true }, vueLoaderConfig)
},
]
},
{
test: /\.sass$/,
exclude: /node_modules/,
loaders: ['style', 'css', 'sass']
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('img/[name].[ext]')
}
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('media/[name].[ext]')
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('fonts/[name].[ext]')
}
}
]
},
plugins: [
// api 统一桥协议方案
new webpack.DefinePlugin({
'mpvue': 'global.mpvue',
'mpvuePlatform': 'global.mpvuePlatform'
}),
new MpvuePlugin(),
new CopyWebpackPlugin([{
from: '**/*.json',
to: ''
}], {
context: 'src/'
}),
new CopyWebpackPlugin([
{
from: path.resolve(__dirname, '../static'),
to: path.resolve(config.build.assetsRoot, './static'),
ignore: ['.*']
}
])
]
}
// 针对百度小程序,由于不支持通过 miniprogramRoot 进行自定义构建完的文件的根路径
// 所以需要将项目根路径下面的 project.swan.json 拷贝到构建目录
// 然后百度开发者工具将 dist/swan 作为项目根目录打
const projectConfigMap = {
tt: '../project.config.json',
swan: '../project.swan.json'
}
const PLATFORM = process.env.PLATFORM
if (/^(swan)|(tt)$/.test(PLATFORM)) {
baseWebpackConfig = merge(baseWebpackConfig, {
plugins: [
new CopyWebpackPlugin([{
from: path.resolve(__dirname, projectConfigMap[PLATFORM]),
to: path.resolve(config.build.assetsRoot)
}])
]
})
}
module.exports = baseWebpackConfig
7.3 使用 vuex
安装 vuex
yarn add vuex@^3.0.1
在你的项目中使用 vuex
创建存储模块 store.ts
// store.ts
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex as any)
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment: (state) => {
const obj = state
obj.count += 1
},
decrement: (state) => {
const obj = state
obj.count -= 1
}
}
})
export default store
使用你创建的存储:
<template>
<div class="counter-warp">
<p>Vuex counter:{{ count }}</p>
<p>
<button @click="increment">+</button>
<button @click="decrement">-</button>
</p>
<a :href="AppUrls.INDEX" class="home">去往首页</a>
</div>
</template>
<script lang="ts">
import { Component, Emit, Vue } from 'vue-property-decorator'
import { AppUrls } from '@/utils/consts.ts'
// 从 store.ts 模块中导入 store
import store from './store'
const debug = require('debug')('log:Page/Counter')
@Component
export default class Counter extends Vue {
AppUrls = AppUrls
// computed
get count () {
return store.state.count
}
increment() {
debug('hello4')
store.commit('increment')
}
decrement() {
store.commit('decrement')
}
}
</script>
<style>
.counter-warp {
text-align: center;
margin-top: 100px;
}
.home {
display: inline-block;
margin: 100px auto;
padding: 5px 10px;
color: blue;
border: 1px solid blue;
}
</style>
8. 构建微信小程序与调试
8.1 生成微信小程序项目
在项目的build
目录下的 build.js
脚本用于生成各种目标项目,其中就包括微信小程序。这个文件的内容是这样子的:
// build/build.js
require('./check-versions')()
process.env.NODE_ENV = 'production'
process.env.PLATFORM = process.argv[process.argv.length - 1] || 'wx'
var ora = require('ora')
var rm = require('rimraf')
var path = require('path')
var chalk = require('chalk')
var webpack = require('webpack')
var config = require('../config')
var webpackConfig = require('./webpack.prod.conf')
var utils = require('./utils')
var spinner = ora('building for production...')
spinner.start()
rm(path.join(config.build.assetsRoot, '*'), err => {
if (err) throw err
webpack(webpackConfig, function (err, stats) {
spinner.stop()
if (err) throw err
if (process.env.PLATFORM === 'swan') {
utils.writeFrameworkinfo()
}
process.stdout.write(stats.toString({
colors: true,
modules: false,
children: false,
chunks: false,
chunkModules: false
}) + '\n\n')
if (stats.hasErrors()) {
console.log(chalk.red(' Build failed with errors.\n'))
process.exit(1)
}
console.log(chalk.cyan(' Build complete.\n'))
console.log(chalk.yellow(
' Tip: built files are meant to be served over an HTTP server.\n' +
' Opening index.html over file:// won\'t work.\n'
))
})
})
如果要构建微信小程序,你需要在你的mpvue项目根目录位置运行命令:
node build/build.js wx
为了方便,一般我们在我们的mpvue项目的package.json
的scripts
中添加快捷命令:
"scripts": {
"build:wx": "node build/build.js wx",
// ...
}
这样,我们以后就只需要在我们 mpvue 项目的根目录下运行这样的简化命令:
yarn build:wx
运行完成后,在我们 mpvue 项目的 dist
中,可以看到生成了一个名字为wx
的文件夹,这就是构建后的我们微信小程序项目的项目目录。
8.2 使用微信开发者工具调试
双击运行你的 微信开发者工具:
点击 导入 按钮:
选取你通过 mpvue 构建的微信小程序,一般在你 mpvue 项目的 dist 目录中。
转载自:https://juejin.cn/post/7240090805313486906