開開心心擼一個VUE轉小程序原生的Webpack-loader(上)

喜迎中華人民共和國成立70週年~~~ 🎵我和個人祖國,一刻都不能分割🎵javascript

前段時間接到一個項目改造任務,須要把Vue項目裏的功能重寫成原生小程序。(驚不驚喜,意不意外) css

在公司裏,這種功能保持不變可是須要寫在不一樣平臺上的問題,其實不少,咱們不可能換一次平臺,手寫一次,耗時耗力在重複性高的工做中,不免會有錯誤。html

最近看了看如何擼一個webpack-loader靈機一動,咱們也能夠寫一個啊,仍是用vue項目開發,打包時生成小程序的文件。這裏,如今只開發到vue文件轉小程序原生,css相對於簡單不少只須要px轉2px。而js就複雜不少了,放在Future完成了,有興趣的小夥伴也能夠加入一塊兒開發啊~一塊兒完成它~前端

爲何用webpack-laoder?

loader 用於對模塊的源代碼進行轉換。loader可使你在 import或"加載"模塊時預處理文件。所以,loader相似於其餘構建工具中「任務(task)」,並提供了處理前端構建步驟的強大方法。 ---摘自官方文檔vue

在這裏我用loader,是由於他有這麼幾個優勢:java

  • loader 接收查詢參數。用於對 loader 傳遞配置。
  • loader 也可以使用 options 對象進行配置。
  • loader 運行在 Node.js 中,而且可以執行任何可能的操做。

接受參數咱們能夠自定義最後生成成什麼平臺的小程序。預處理,咱們能夠在打包前拿到文件內容利用vue的插件解析出templatecssjavascript三個文件,從而進一步取處理。node

第一節,咱們先把項目搭起來~ let't Go !webpack

在vue項目中搭起環境

我本身在項目中建立了一個task文件,專門用來開發這個webpack-laoder,本身簡單的配置了webpack。 web

接下倆簡單介紹一下,裏面3個文件的做用。shell

build.js

build.js用途就是調用wenpack輸出咱們小程序文件

const shell = require('shelljs') 
const { resolve } = require('path')
const fs = require('fs')
const webpack = require('webpack')
const _ = require('lodash')
const r = url => resolve(process.cwd(), url)
const config = require('./config') //咱們抽出的小程序主要的文件
const webpackConf = require('./webpack.conf') // webpack配置

const assetsPath = config.assetsPath

shell.rm('-rf', assetsPath)
shell.mkdir(assetsPath)

const renderConf = webpackConf
const entry = () => _.reduce(config.json.pages, (en, i) => {
  en[i] = resolve(__dirname, '../src/components/', `HelloWorld.vue`) //須要轉成小程序的文件的地址
  return en
}, {}) //輸出一個對象

renderConf.output = { //輸出文件的配置
  path: config.assetsPath,
  filename: '[name].js'
}

renderConf.entry = entry()
// renderConf.entry.app = config.app

// 若是你不傳入回調函數到 webpack 執行函數中,就會獲得一個 webpack Compiler 實例。你能夠經過它手動觸發 webpack 執行器,或者是讓它執行構建並監聽變動。和 CLI API 很相似。Compiler 實例提供瞭如下方法
const compiler = webpack(renderConf) //導入的 webpack 函數須要傳入一個 webpack 配置對象,當同時傳入回調函數時就會執行 webpack 

fs.writeFileSync(resolve(config.assetsPath, './app.json'), JSON.stringify(config.json), 'utf8')//一步寫入文件小程序的app.json
   
const callback = (err, stats) => {
    console.log('Compiler 已經完成執行。');
    if (err) process.stdout.write(err) //若是有報錯,就在控制檯打印出報錯
};

compiler.run(callback)
複製代碼

webpack.conf.js

webpack的配置項

const { resolve } = require('path') //方法會把一個路徑或路徑片斷的序列解析爲一個絕對路徑。
const r = url => resolve(__dirname, url)
const webpack = require('webpack')
const CopyWebpackPlugin = require('copy-webpack-plugin') //將單個文件或整個目錄複製到構建目錄
const ProgressBarPlugin = require('progress-bar-webpack-plugin') //打包時候在命令行裏的進度條
const ExtractTextPlugin = require('extract-text-webpack-plugin') //打包的時候分離出文本,好比css,打包到單獨的文件夾裏

const extractSass = new ExtractTextPlugin({
  filename: '[name].wxss'
})

const config = require('../config')

module.exports = {
  devtool: false,
  output: {
    path: config.assetsPath,
    filename: '[name].js'
  },
  resolve: {
    alias: {
      utils: r('../utils/utils')
    }
  },
  resolveLoader: {
    // 去哪些項目下尋找Loader,有前後順序之分,這裏是爲了本地調試laoder方便
    modules: ['node_modules', './loaders/'],
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        loader: 'babel-loader',
        exclude: /node_modules/,
        options: {
          presets: [
            'latest'
          ]
        }
      },
      {
        test: /\.vue$/,
        loader: 'program-loader', //這個就是咱們變成的小程序laoder
        options: {
          dist: './program',
          type:''
        }
      }
    ]
  },
  plugins: [
    extractSass,
    new CopyWebpackPlugin([
      {
        from : {
          glob: 'pages/**/*.json',
          to: ''
        } 
      }, {
        from: 'static',
        to: 'static'
      }
    ]),
    new webpack.optimize.ModuleConcatenationPlugin(),//解釋是啓用做用域提高,webpack3新特性,做用是讓代碼文件更小、運行的更快
    new ProgressBarPlugin()
  ]
}

複製代碼

config.js

這個文件是配置小程序生成的json文件,公共style等。

const { resolve } = require('path')
const r = url => resolve(__dirname, url)
const assetsPath = resolve(process.cwd(), './program') 

module.exports={
    "json":{ //經過這個配置小程序的頁面
        "pages":[
          "pages/home/home",
        ],
        "window":{
        }
    },
    "style":{
        url:r('./style/common.less'),
        lang:'less'
    },
    "assetsPath": assetsPath,
}

複製代碼

運行就靠一行代碼 node task/build.js 到這裏vue項目裏的環境配置就作好了,這裏你須要知道一點本地調試wenpack-loader的知識 加載調試本地loader

program-loader

接下來,咱們將具體解析program-loader裏的代碼,這裏我將內容分紅3個部分,第一部分介紹如何解析vue文件(最簡單的一部分),第二部分解析將vue中的template解析成一個nodeTree,第三部分也是比較重要的將nodeTree再變成小程序中的原生標籤。

因此,咱們開始第一部分吧。

先看一下program-loader的項目結構

render-css.js用於解析樣式文件,render-html.js用於解析html標籤,render-script.js解析js文件。runfile.js用來輸出html的文件,template.js用來解析nodeTree轉成小程序原生標。 index.js啓動文件。

index.js用到一個解析vue文件的插件vue-template-compiler能夠將vue分爲html,css,script。同時,咱們還能夠利用loader的異步性和緩存機制充分的來提升編譯效率。

const renderHtml = require('./lib/render-html')
const renderCss = require('./lib/render-css')
const renderScript = require('./lib/render-script')
const parseTemplate = require('./lib/template')
const runfile = require('./lib/runfile')
const {
  parseComponent
} = require('vue-template-compiler')
module.exports = function (content) {
  this.cacheable() //緩存 webpack充分地利用緩存來提升編譯效率
  var cb = this.async() // 異步 當一個 Loader 無依賴,可異步的時候我想都應該讓它再也不阻塞地去異步
  const parts = parseComponent(content) //解析vue的格式
  let { template= {} } = parts
  
  if (template) {
    try{
      let nodes = renderHtml.parse(template.content)
      let wxml = parseTemplate.outputWxml(nodes)
      runfile.call(this, wxml)  
    }
    catch(err){
      console.log(err)
    }

  }
  if (parts.styles && parts.styles.length) {
    renderCss.call(this, parts.styles[0])  
  }
  if (parts.script) {
    renderScript.call(this, parts.script, cb)
    return
  } else {
    cb(null, '')
  }
}

複製代碼

render-css.js解析樣式的時候,我用less轉成css,固然這裏並不限制你用什麼來編寫,你記得lang標示出你用的語言就能夠~ 而後利用px2rpx去轉rpx。這裏很簡單,你很容易就能看懂。

const loaderUtils = require('loader-utils')
const fs = require('fs-extra')
const { resolve } = require('path')
const Px2rpx = require('px2rpx');
const px2rpxIns = new Px2rpx({ rpxUnit: 0.5 });

const con = {
  stylus: (file, data) => new Promise(resolve => {
    require('stylus').render(data, { filename: file }, (err, css) => {
      if (err) throw err
        
      resolve(css)
    }) 
  }),
  less: (file, data) => new Promise(resolve => {
    require('less').render(data, {}, (err, result) => {
      if (err) throw err
      let css = result.css
      resolve(px2rpxIns.generaterpx(css))
    }) 
  }),
  scss: (file, data) => new Promise(resolve => {
    require('node-sass').render({
      file, 
      data,
      outputStyle: 'compressed'
    }, (err, result) => {
      if (err) throw err

      resolve(result.css)
    }) 
  }),
  sass: (file, data) => new Promise(resolve => {
    require('node-sass').render({
      file, 
      data,
      outputStyle: 'compressed',
      indentedSyntax: true
    }, (err, result) => {
      if (err) throw err

      resolve(result.css)
    }) 
  })
}


module.exports = async function (style) {
  this.cacheable()


  const options = loaderUtils.getOptions(this)
  const file = options.type === 'wx' ? "[name].wxss" :"[name].acss" ;
  const pullPath = loaderUtils.interpolateName(this, `[path][name].program`, options)
  const filename = loaderUtils.interpolateName(this, file, options)
  const folder = loaderUtils.interpolateName(this, `[folder]`, options)
  const dist = options.dist || 'dist'

  let stylesheet = style.content
  let lang = style.lang
  if (lang) {
    const render = con[style.lang]

    stylesheet = await render(pullPath, stylesheet)
  }
  fs.outputFileSync(resolve(process.cwd(), `${dist}/pages/${folder}/${filename}`), stylesheet)

  return ``
}


複製代碼

render-script.js我沒有作什麼操做,就是編譯後輸出了。這裏尚未去研究 = =|||。

const { transform } = require('babel-core')

module.exports = function (script, cb) {
  this.cacheable()
  // 獲取當前用戶給當前loader傳入的參數對象options
  cb(null, script.content)
}

複製代碼

到這裏,第一部分應該沒啥問題了,接下來,咱們要進入最重要的第二部分了~

稍等,由於篇幅較長,我分紅了上下段。等我。。。就回來。。。

相關文章
相關標籤/搜索