終極蛇皮上帝視角之微信小程序之告別「刀耕火種」

開門見山地說,小程序在平常開發中使用原生框架來開發仍是挺不方便的,好比:css

  • 不支持 npm
  • 不支持各類 CSS 預編譯器
  • 不支持配置 Babel 來轉換一些 JavaScript 新特性

這樣一來和平常開發前端頁面的體驗相比來講,簡直就像在刀耕火種html

那麼爲了解決這些問題,咱們能不能將前端開發中經常使用的 webpack 移植到小程序開發中呢?

固然能夠!前端

0.源碼地址


  • webpack-simple 中文件結構和小程序類似。
  • 而在 webpack-vue 中還增長了 vue-loader,所以你甚至還能利用 .vue 文件編寫單文件組件。
注:已封裝到 https://tuateam.github.io/tua... 中...

1.文件結構

既然用 webpack 來編譯源代碼,那麼很天然的咱們的文件結構首先要分爲 src/dist/,開發者工具的目標應該是 dist/ 目錄。vue

注:開發者工具打開的應該是根目錄,這樣能夠保存各類設置,能夠在 project.config.json 中配置 "miniprogramRoot": "./dist/",

1.1.src/ 中文件結構大概長這樣:

.
├── app
│   ├── app.js
│   ├── app.json
│   └── app.scss
├── assets
│   └── vue-logo.png
├── comps
│   └── todo
│       ├── todo.js
│       ├── todo.json
│       ├── todo.less
│       └── todo.wxml
├── pages
│   └── index
│       ├── index.js
│       ├── index.json
│       ├── index.less
│       └── index.wxml
├── scripts
│   ├── const
│   │   ├── README.md
│   │   └── index.js
│   └── utils
│       ├── README.md
│       ├── event.js
│       ├── format.js
│       ├── index.js
│       └── log.js
├── styles
│   ├── global.styl
│   ├── todomvc-app-css.css
│   └── todomvc-common-base.css
└── templates
    └── info.wxml
  • app/: 應用入口
  • assets/: 資源文件,好比圖片
  • comps/: 組件
  • pages/: 頁面
  • scripts: 公用代碼
  • scripts/const: 常量(已配置別名 @const)
  • scripts/utils: 輔助函數(已配置別名 @utils)
  • styles/: 公用樣式
  • templates/: 模板

1.2.dist/ 中文件結構大概長這樣:

.
├── app.js
├── app.js.map
├── app.json
├── app.wxss
├── assets
│   └── vue-logo.png
├── chunks
│   ├── runtime.js
│   ├── runtime.js.map
│   ├── scripts.js
│   ├── scripts.js.map
│   ├── vendors.js
│   └── vendors.js.map
├── comps
│   └── todo
│       ├── todo.js
│       ├── todo.js.map
│       ├── todo.json
│       ├── todo.wxml
│       └── todo.wxss
├── pages
│   └── index
│       ├── index.js
│       ├── index.js.map
│       ├── index.json
│       ├── index.wxml
│       └── index.wxss
└── templates
    └── info.wxml

1.3.整個項目文件結構大概長這樣:

.
├── README.md
├── dist/
├── package.json
├── project.config.json
├── src/
├── webpack.config.babel.js
└── yarn.lock
  • src/: 源碼
  • dist/: 打包後代碼

2.webpack 基礎配置

2.1.entry/output

小程序場景下的配置應該是多入口,主要分爲 apppagescomps 這三類。webpack

  • app: 將 src/app/ 下的文件編譯成 dist/ 根目錄下的 app.js/app.json/app.wxss
  • pages: src/pages/ -> dist/pages/
  • comps: src/comps/ -> dist/comps/

在輸出 output 部分有個坑:由於小程序使用的是 global,因此必須添加配置 output.globalObjectglobalgit

否則...
thirdScriptError VM937:1
 sdk uncaught third Error
 Cannot read property 'webpackJsonp' of undefined
 TypeError: Cannot read property 'webpackJsonp' of undefined
    at http://127.0.0.1:40247/appservice/chunks/runtime.js:34:51
    at http://127.0.0.1:40247/appservice/chunks/runtime.js:38:2
    at require (http://127.0.0.1:40247/appservice/__dev__/WAService.js:19:7859)
    at http://127.0.0.1:40247/appservice/__dev__/WAService.js:19:7573
    at http://127.0.0.1:40247/appservice/app.js:3:1
    at require (http://127.0.0.1:40247/appservice/__dev__/WAService.js:19:7859)
    at http://127.0.0.1:40247/appservice/appservice?t=1527755092895:1020:9


// runtime
var a = window.webpackJsonp = window.webpackJsonp || []

詳情可參閱這個 prgithub

ps 在 mpvue 中彷佛是經過修改 target 實現的... http://mpvue.com/build/mpvue-...

2.2.CommonChunk

在 webpack 4 中有一個 breaking change,即便用 SplitChunksPlugin 替換了以前很經常使用的 CommonsChunkPluginweb

主要提取了三部分的公共代碼:npm

如今又碰到個新的問題:如何引入這些 chunks

在前端項目中通常咱們經過 HtmlWebpackPlugin 插件在 html 文件中添加 <script> 標籤引入,然鵝小程序中並無 html 文件...

計將安出?

總不能每次都手動去 dist/app.js 中 require 這些文件吧?

這時候就要介紹另外一款插件了~:BannerPlugin

這個插件原本是用在文件頭部添加 banner 的,可是也支持插入代碼,所以利用這款插件咱們就能夠將這些公共依賴在 app.js 中統一引入一次便可。

TODO: 現版本的小程序提供了分包加載能力,所以這裏還有優化空間

2.3.CopyWebpackPlugin

顧名思義,這款插件的用處就是拷貝,利用這款插件咱們就能夠實現:

  • 複製 *.json
  • 複製 *.wxml
  • 複製 *.wxss
  • 複製 assets/
  • 複製 templates/

在使用時有一個知識點能夠減小代碼量:即 context 選項,這樣就不用寫 n 個 src/了...

new CopyWebpackPlugin(copyCfgArr, {
    context: resolve('src'),
}),

2.4.預處理器和 CSS 的處理

這部分其實都是常規操做和通常 web 開發沒啥區別,配置好對應的 loader 便可。

須要注意的點就是必定要使用 ExtractTextWebpackPlugin 插件來生成 .wxss 文件。

new ExtractTextPlugin('[name].wxss')
注:已換成 mini-css-extract-plugin

3.webpack + vue-loader

這部分談談如何利用 vue-loader 實如今小程序中引用單文件組件(.vue)。

先看看 src/ 下的文件結構:

.
├── app
│   ├── App.vue
│   ├── app.js
│   └── app.json
├── assets
│   └── vue-logo.png
├── comps
│   ├── Filter
│   │   ├── Filter.vue
│   │   └── index.js
│   └── Todo
│       ├── Todo.vue
│       └── index.js
├── pages
│   ├── index
│   │   ├── Index.vue
│   │   └── index.js
│   └── todos
│       ├── Todos.vue
│       └── index.js
├── scripts
│   ├── const
│   │   ├── README.md
│   │   └── index.js
│   └── utils
│       ├── README.md
│       ├── event.js
│       ├── format.js
│       ├── index.js
│       └── log.js
├── styles
│   ├── global.styl
│   ├── todomvc-app-css.css
│   └── todomvc-common-base.css
└── templates
    └── info.wxml

其實已經和通常的 web 項目很類似了~

3.1.vue-loader v15?

隨着 webpack 升級到了 v4,官方與之配合的 vue-loader 也升級到了 v15。

如今 Vue Loader 15 使用了一個不同的策略來推導語言塊使用的 loader。

在 v15 中,<style lang="less"> 會完成把它看成一個真實的 *.less 文件來加載。所以,爲了這樣處理它,你須要在你的主 webpack 配置中顯式地提供一條規則。

簡單來講就是我們以前配置過的各個預處理器規則會被 vue-loader 自動使用。

所以咱們只須要簡單地添加一條規則便可讀取 .vue 文件:

{
    test: /\.vue$/,
    exclude: /node_modules/,
    loader: 'vue-loader',
    options: {
        compiler: {
            // mock vue-template-compiler
            compile: () => ({
                staticRenderFns: [],
            })
        },
    },
},
options.compiler 是啥?
注意:隨着 vue-loader 的升級,這部分的 mock 有變化...
options: {
    // mock vue-template-compiler
    compile: () => ({
        staticRenderFns: [],
    }),
    parseComponent: require('vue-template-compiler')
        .parseComponent,
    },
},

3.2.options.compiler

options.compiler 覆寫用來編譯單文件組件中 <template> 塊的默認編譯器。

在實際使用單文件組件時,咱們經過 <template lang="wxml"> 來包裹本來的 .wxml 文件中的內容。

由於最終要編譯成 .wxml 文件才能被開發者工具識別,因此咱們還編寫了一條規則經過 file-loader 生成最終的 .wxml 文件:

{
    // 處理 <template lang="wxml">{...}</template>
    // 生成 .wxml 文件
    test: /\.wxml$/,
    use: {
        loader: 'file-loader',
        options: {
            name: getNameByFilePathAndExt('.wxml'),
        },
    },
},

可是由於 vue-loader 默認會編譯 template 中的內容將其生成一個個 render 函數。但其實在小程序場景中咱們並不須要這一步驟。咱們只想安安靜靜地將這些代碼經過 file-loader 生成 .wxml 文件...

幸虧 vue-loader 還提供了 options.compiler 這個參數用來傳遞本身的編譯器。因此這裏實際上是 mock 了一下 vue-template-compiler

3.3.Custom Blocks

最後還有個問題沒有解決:如何處理 .json 文件?

在其餘的小程序框架中是這樣處理的:

  • wepy 中將其做爲組件的 config 屬性
export default class Index extends wepy.page {
    //頁面配置
    config = {
        "navigationBarTitleText": "test"
    };

    // ...
}
  • mpvue 中是寫在 main.js 的輸出部分
// main.js
export default {
  // 這個字段走 app.json
  config: {
    // 頁面前帶有 ^ 符號的,會被編譯成首頁,其餘頁面能夠選填,咱們會自動把 webpack entry 裏面的入口頁面加進去
    pages: ['pages/logs/main', '^pages/index/main'],
    window: {
      backgroundTextStyle: 'light',
      navigationBarBackgroundColor: '#fff',
      navigationBarTitleText: 'WeChat',
      navigationBarTextStyle: 'black'
    }
  }
}

// src/pages/logs/main.js
export default {
  config: {
    navigationBarTitleText: '查看啓動日誌'
  }
}

tua-mp 中目前採用的是自定義塊的方式來實現的,即在 .vue 文件中新增了一個 <config> 塊來編寫配置。

<config>
{
  "navigationBarTitleText": "查看啓動日誌"
}
</config>

<template lang="wxml">
    ...
</template>

可是並無將 app.json 的內容放到 App.vue 中,由於有時須要讀取這裏的頁面配置。若是寫到 <config> 中的話,就沒法讀取了...

例如爲了實現從分享後的頁面後退返回首頁這個功能,在輔助函數中就須要讀取頁面和 tabBar 配置,生成分享連接(實際分享地址是首頁,而後從首頁再導航到被分享的頁面)。

所以最優解是頁面配置寫在 <config> 中,應用配置寫在 app.js 的輸出中。

TODO: 實現 mpvue 的方式處理 app.json

具體的配置以下:

{
    // 處理 <config>{...}</config> 代碼塊
    // 生成 .json 文件
    resourceQuery: /blockType=config/,
    use: {
        loader: 'file-loader',
        options: {
            name: getNameByFilePathAndExt('.json'),
        },
    },
},

4.總結

綜上,我們在 webpack v4vue-loader v15 的幫助下,讓小程序擁有了如下能力:

  • 加載 npm 包
  • 提取 CommonChunk 減小打包體積
  • babel 編譯 JavaScript 代碼
  • 支持 less/sass/stylus 等預處理器
  • 單文件組件
不過話又說回來了...

原生的小程序...又不是不能用~

注:這句話是黃章說的,Teacher Luo 沒說過這話喲~

以上 to be continued...

相關文章
相關標籤/搜索