簡介
有時間研究下開源庫的源碼,總是會有些收穫的。注意到 Atom 插件編寫時,可以直接使用 babel, coffeescript 或者 typescript。有些詫異,畢竟 Electron 中內置的 node 引擎,也一定不是完全兼容 es6,更不用說 coffeescript 和 typescript了。所以,必然在加載插件時,Atom 有某種自動轉換的操作。剛好最近有一些類似的需求,需要批量以單個文件的方式轉換一些其他語法的文件到 es5 兼容的js文件,於是就把 Atom 的轉換機制拆分了出來,寫成一個 cli。
他山之玉,不敢私藏。如果只是使用,請直接在npmjs上查找:smart-transform
特色定製
毋容置疑,最核心的地方是取自於 Atom 本身。之所以把這個邏輯單獨剝離出來,主要是我很羨慕 Atom 插件編寫時,各種語法隨心使用的舒爽!要是自己項目,也能這麼隨意,豈不是爽歪歪!!!
爲了獨立於 Atom 使用,同時又具備一定的通用新,主要定製性體現在:
- 將邏輯剝離成一個 cli 命令行工具,以後不管自己還是別人,拿來即用。不是每個前端,都很擅長 nodejs,所以我覺得,這還是能方便一些人的。
- 通過配置文件,允許個性化定製。即,每個項目的輸入和輸出目錄可以通過配置文件來自由配置。現在還不夠靈活,只支持指定唯一一個輸入文件夾和唯一一個輸出文件夾,不過暫時夠用了。
- 引入 uglify-js 進行壓縮和混淆。這一點,確實是項目本身的需要,我相信大部分人,都有這個需求吧?另外,之所以直接使用 uglify-js ,當然是因爲我不想再額外配置 webpack 呀!!
扔一個 smart-transform.json 配置文件示例上來吧:
{
"in":"./src",
"out":"./lib",
"exclude":["./src/hi-ignore.js"],
"minify":true,
"minifyExclude":["./src/hi-ts.ts"]
}
源碼解讀
package.json
"bin": {
"smart-transform": "index.js"
}
比較特殊的是 bin 字段。第一次寫 cli 的童鞋,常常因爲沒有寫這個字段,導致沒有以全局命令的形式使用自己的工具庫。
index.js
這是定製最多的一個文件。它實現的主要功能是,讀取具體項目根目錄的配置文件 smart-transform.json ,然後根據內部字段,來進行一些個性化的轉換操作。
目前支持的操作有:
- 將指定目錄的 babeljs/coffeescript/typescript 轉爲 es5 兼容的js文件,並輸出到另一個目錄。
- 忽略某些文件,不對其進行轉換操作。
- 轉換時,可選支持同時進行壓縮和混淆操作。壓縮和混淆,目前使用的是 uglify-js
代碼不長,但是本身有一些 node 相關的代碼,所以我就還是貼出來,感興趣的順便瞅一眼:
#!/usr/bin/env node
'use strict'
var path = require("path")
var fs = require ('fs-plus')
var fse = require('fs-extra')
var os = require("os")
var {execSync} = require("child_process")
var UglifyJS = require("uglify-js")
var argv = require('minimist')(process.argv.slice(2))
var project = argv.project
var configInfo = require(path.resolve(project,"./smart-transform.json"))
var inDir = path.resolve(project,configInfo.in)
var outDir = path.resolve(project,configInfo.out)
var minify = configInfo.minify
var excludeFiles = configInfo.exclude.map(function (filePath) {
return path.resolve(project,filePath)
})
var minifyExcludeFiles = configInfo.minifyExclude.map(
function (filePath) {
return path.resolve(project,filePath)
}
)
fse.ensureDirSync(outDir)
var inFiles = fs.listSync(inDir,[".js",".ts","coffee"])
for (var inFile of inFiles) {
if (excludeFiles.includes(inFile)) { // 不需要處理的,直接複製到輸出目錄
var outFile = path.resolve(project,outDir,path.basename(inFile))
fse.copySync(inFile,outFile)
continue
}
var sourceCode = require("./compile-file")(inFile)
if (minify && !minifyExcludeFiles.includes(inFile)) {
sourceCode = UglifyJS.minify(sourceCode).code
}
var outFile = path.resolve(project,outDir,path.basename(inFile,path.extname(inFile)) + ".js")
fse.ensureFileSync(outFile)
fs.writeFileSync(outFile,sourceCode)
}
compile-file.js
相關預編譯邏輯取自原Atom代碼中的 src/compile-cache.js 類,主要區別是,禁用代碼地圖並禁用輸出代碼內的註釋。考慮到項目本身的內部兼容性,並沒有直接使用最新版的 Atom 源碼演繹。如果自己有其他定製需求,可以直接看 Atom 源碼。
這個文件比較出彩的地方是,它把各種類似的語法都使用 COMPILERS 的機制管理。一種語法對應一個 COMPILER。在某些特定情況下,如果你想解析或轉換其他類型的文件,只需要修改這個類,新增一個 COMPILER 即可。
'use strict'
var path = require('path')
var fs = require('fs-plus')
var COMPILERS = {
'.js': require('./babel'),
'.ts': require('./typescript'),
'.coffee': require('./coffee-script')
}
function compileFileAtPath (filePath) {
const extension = path.extname(filePath)
const compiler = COMPILERS[extension]
var sourceCode = fs.readFileSync(filePath, 'utf8')
if (compiler.shouldCompile(sourceCode, filePath)) {
const compiledCode = compiler.compile(sourceCode, filePath)
return compiledCode
}
return sourceCode
}
module.exports = compileFileAtPath
babel.js coffee-script.js typescript.js
分別取自 Atom 源碼中的 babel.js coffee-script.js typescript.js。有極小的修改,典型的 拿來主義 。有興趣的,直接去看下源碼,此處不做贅述。
注意
使用 bable 的js文件,開頭應是以下幾種的其中一種,否則無法被識別:
/** @babel */
"use babel"
'use babel'
/* @flow */