likes
comments
collection
share

小程序开发.mpvue.项目构建与运行

作者站长头像
站长
· 阅读数 16
小程序开发.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 的主要特性

2. mpvue 项目结构

3. mpvue-loader

4. TypeScript 支持

5. Sass 支持

6. less 支持

7. 手把手创建一个 mpvue 项目

8. 构建微信小程序与调试


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 项目,部分目录已经展开: 小程序开发.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.jsonpage.jsproject.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.jsapp.jsonapp.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-componentvue-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>

★ 注意:

  1. 你也可以将 typescript 脚本写到单独的文件中,比如:
<script lang="ts" src="./app.ts"></script>
  1. 如果你要想上面那样,直接在 .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 支持

安装 lessless-loader

yarn add less less-loader -D

webpack.base.conf.jsmodulerules下增加规则:

      {
        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

小程序开发.mpvue.项目构建与运行 以上选项可以根据你的需求来,也可以就使用默认值。创建完成后,进入项目,并安装依赖:

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.jsmodule.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.jsonscripts中添加快捷命令:

"scripts": {
  "build:wx": "node build/build.js wx",
  // ...
}

这样,我们以后就只需要在我们 mpvue 项目的根目录下运行这样的简化命令:

yarn build:wx

运行完成后,在我们 mpvue 项目的 dist 中,可以看到生成了一个名字为wx的文件夹,这就是构建后的我们微信小程序项目的项目目录。

8.2 使用微信开发者工具调试

双击运行你的 微信开发者工具小程序开发.mpvue.项目构建与运行 点击 导入 按钮: 小程序开发.mpvue.项目构建与运行 选取你通过 mpvue 构建的微信小程序,一般在你 mpvue 项目的 dist 目录中。

转载自:https://juejin.cn/post/7240090805313486906
评论
请登录