From 7ce8c763f09dbcb09bf3608ffca12b1a95a0c4b8 Mon Sep 17 00:00:00 2001 From: vagusX Date: Mon, 21 Sep 2020 17:12:51 +0800 Subject: [PATCH] feat: add some language utils --- README.md | 5 + index.d.ts | 23 +++ package.json | 3 +- scripts/generate.js | 145 ++++++++++++++---- .../entry/index-es.js} | 0 .../entry/index.js} | 0 .../loader/index-es.js} | 0 .../loader.js => template/loader/index.js} | 0 scripts/template/utils/index-es.js | 24 +++ scripts/template/utils/index.js | 24 +++ 10 files changed, 190 insertions(+), 34 deletions(-) rename scripts/{entry-template/entry-template-es.js => template/entry/index-es.js} (100%) rename scripts/{entry-template/entry-template.js => template/entry/index.js} (100%) rename scripts/{loaders/loader-es.js => template/loader/index-es.js} (100%) rename scripts/{loaders/loader.js => template/loader/index.js} (100%) create mode 100644 scripts/template/utils/index-es.js create mode 100644 scripts/template/utils/index.js diff --git a/README.md b/README.md index 8bb4d11..405cacf 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,11 @@ * `"configuration": "./language-configuration.json"` --> `"resovledConfiguration": require('./language-configuration.json')` * `"path": "./syntaxes/JavaScript.tmLanguage.json"` --> `"resovledConfiguration": require('./syntaxes/JavaScript.tmLanguage.json') +### 加载策略 +* 在知道需要加载文件后缀之后可以按照文件后缀去选择需要加载的插件包即可 +* 可通过 `es/utils#hasLanguageId` 方法去判断是否支持对应的语言 id +* 可通过 `es/utils#getLanguageIdByExtnameAndFilename` 方法传递拓展名和文件名去获取对应的语言 desc,从 `extensionPackageName` 字段可看出对应的语言包是哪一个 + ### tmLanguage 文件 由于 require 语法导致在 webpack 中需要额外添加 raw-loader 去处理 tmLanguage 文件,且会导致 resovledConfiguration 的内容格式不统一,因此目前都通过 fork vscode 插件 [TextMate Languages] 将 tmLanguage 转成 json diff --git a/index.d.ts b/index.d.ts index 57e564b..6954c5d 100644 --- a/index.d.ts +++ b/index.d.ts @@ -4,3 +4,26 @@ declare module '@ali/kaitian-textmate-languages' { registerGrammar: (grammarContribution: any, extPath: any) => Promise, ) => Promise[]; } + +interface LanguageDesc { + id: string; + extensions: string[]; + aliases: string[]; + configuration: string; + filenames?: string[]; + firstLine?: string; + /** + * 拓展字段,用来标识当前 language 是来源于哪个插件语言包 + */ + extensionPackageName?: string; +} + +declare module '@ali/kaitian-textmate-languages/es/utils' { + export function getLanguageIdByExtnameAndFilename(extname: string, filename?: string): LanguageDesc | undefined; + export function hasLanguageId(languageId: string): boolean; +} + +declare module '@ali/kaitian-textmate-languages/lib/utils' { + export function getLanguageIdByExtnameAndFilename(extname: string, filename?: string): LanguageDesc | undefined; + export function hasLanguageId(languageId: string): boolean; +} diff --git a/package.json b/package.json index 422c4f5..2f80edc 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,8 @@ "name": "@ali/kaitian-textmate-languages", "version": "2.0.1", "description": "", - "main": "index.js", + "main": "lib/loader.js", + "module": "es/loader.js", "scripts": { "download": "node scripts/update-extensions.js", "generate": "rm -rf lib && node scripts/generate.js", diff --git a/scripts/generate.js b/scripts/generate.js index 5d839c4..ccae82a 100644 --- a/scripts/generate.js +++ b/scripts/generate.js @@ -8,8 +8,8 @@ const prettier = require('prettier') const bluebird = require('bluebird') const stripJsonComments = require('strip-json-comments') -const entryTemplate = require('./entry-template/entry-template') -const entryTemplateEs = require('./entry-template/entry-template-es') +const entryTemplate = require('./template/entry') +const entryTemplateEs = require('./template/entry/index-es') const extensionsDir = path.resolve(__dirname, '../extensions') const targetDir = path.resolve(__dirname, '../lib') @@ -19,6 +19,11 @@ templateSettings.escape = false const ResolvedConfigFiled = 'resolvedConfiguration'; +// format by prettier +function pretty(content) { + return prettier.format(content, require('../.prettierrc')); +} + class Extension { constructor(extPath) { this.extPath = extPath @@ -32,17 +37,19 @@ class Extension { const extPkgJson = await fse.readJSON( path.resolve(this.extPath, './package.json') ) - await this.contributes(extPkgJson, targetDir, false) - await this.contributes(extPkgJson, targetEsDir, true) + // 将 package.json 内容挂到 Extension 实例上 + this.pkgJson = extPkgJson + await this.contributes(targetDir, false) + await this.contributes(targetEsDir, true) } - async contributes(extDesc, outDir, esMode = false) { + async contributes(outDir, esMode = false) { // create a folder for ext - const extOutDir = path.join(outDir, extDesc.name) + const extOutDir = path.join(outDir, this.pkgJson.name) await fse.ensureDir(extOutDir) // handle languages/grammars - await this.collectLanguages(extDesc.contributes, extOutDir) - await this.collectGrammars(extDesc.contributes, extOutDir) + await this.collectLanguages(this.pkgJson.contributes, extOutDir) + await this.collectGrammars(this.pkgJson.contributes, extOutDir) await this.writeEntry(extOutDir, esMode) } @@ -81,8 +88,7 @@ class Extension { await fse.writeFile( path.resolve(extOutDir, 'index.js'), - // format by prettier - prettier.format(content, require('../.prettierrc')), + pretty(content), 'utf8' ) } @@ -98,26 +104,35 @@ class Extension { await bluebird.map( languages, language => { - if (language.configuration) { - const targetFilename = path.basename(language.configuration) - // 收集 language 并将 configuration#string 转成 require 语法 - this.desc.languages.push({ - ...language, - configuration: './' + targetFilename.trim() - }) - - return this.copyFileWithoutComments( - path.resolve(this.extPath, language.configuration), - path.resolve(extOutDir, targetFilename) - ) + if (!language.configuration) { + this.collectLanguage(language) + return } - // 收集 language - this.desc.languages.push(language) + + const targetFilename = path.basename(language.configuration) + // 收集 language 并将 configuration#string 转成 require 语法 + this.collectLanguage({ + ...language, + configuration: './' + targetFilename.trim() + }) + + return this.copyFileWithoutComments( + path.resolve(this.extPath, language.configuration), + path.resolve(extOutDir, targetFilename) + ) }, { concurrency: 3 } ) } + collectLanguage(language) { + // 因为上方执行了两次 collect contributes 因此做了一个去重 + if (!this.desc.languages.find(n => n.id === language.id)) { + // 收集 language + this.desc.languages.push(language) + } + } + // handle textmate grammars#path async collectGrammars(contributes = {}, extOutDir) { const { grammars } = contributes @@ -169,30 +184,94 @@ class Extension { } } +async function copyDummyFiles(filename, content, esContent) { + // generate `grammar-list.js` + await fse.writeFile( + path.resolve(targetDir, filename), + pretty(content), + ) + await fse.writeFile( + path.resolve(targetEsDir, filename), + pretty(esContent), + ) +} + +/** + * 将 language-list/grammar-list 生成 + */ +async function generateListFiles(extMetaList) { + const languageList = [] + const grammarList = [] + // 将 language/grammar 收集 + extMetaList + .forEach(extMeta => { + const { languages, grammars } = extMeta.toJSON() + const { name: extensionPackageName } = extMeta.pkgJson + for (const language of languages) { + languageList.push({ + ...language, + extensionPackageName, + }) + } + + for (const grammar of grammars) { + grammarList.push({ + ...grammar, + extensionPackageName, + }) + } + }) + + // 排序 + languageList.sort((a, b) => a.id - b.id) + grammarList.sort((a, b) => a.language - b.language) + // 将收集好的 language/grammar 生成一个文件 + // generate `language-list.js` + await copyDummyFiles( + './language-list.js', + `module.exports = ${JSON.stringify(languageList)}`, + `export default ${JSON.stringify(languageList)}`, + ) + + // generate `grammar-list.js` + await copyDummyFiles( + './grammar-list.js', + `module.exports = ${JSON.stringify(grammarList)}`, + `export default ${JSON.stringify(grammarList)}`, + ) + + // copy `util.js` + await copyDummyFiles( + './utils.js', + await fse.readFile(path.resolve(__dirname, './template/utils/index.js'), 'utf-8'), + await fse.readFile(path.resolve(__dirname, './template/utils/index-es.js'), 'utf-8'), + ) +} + async function generate() { try { for (const dir of [targetDir, targetEsDir]) { await fse.emptyDir(dir) } // copy `loader.js` - await fse.copyFile( - path.resolve(__dirname, './loaders/loader.js'), - path.resolve(targetDir, './loader.js') - ) - - // copy `loader-es.js` - await fse.copyFile( - path.resolve(__dirname, './loaders/loader-es.js'), - path.resolve(targetEsDir, './loader.js') + await copyDummyFiles( + 'loader.js', + await fse.readFile(path.resolve(__dirname, './template/loader/index.js'), 'utf-8'), + await fse.readFile(path.resolve(__dirname, './template/loader/index-es.js'), 'utf-8'), ) const extensionNames = await promisify(fs.readdir)(extensionsDir) + + const extensionList = [] for (const extName of extensionNames) { // read extension package.json const extPath = path.resolve(extensionsDir, extName) const extension = new Extension(extPath) await extension.run() + extensionList.push(extension) } + // 生成 meta 文件 + await generateListFiles(extensionList) } catch (err) { console.log(err) process.exit(128) diff --git a/scripts/entry-template/entry-template-es.js b/scripts/template/entry/index-es.js similarity index 100% rename from scripts/entry-template/entry-template-es.js rename to scripts/template/entry/index-es.js diff --git a/scripts/entry-template/entry-template.js b/scripts/template/entry/index.js similarity index 100% rename from scripts/entry-template/entry-template.js rename to scripts/template/entry/index.js diff --git a/scripts/loaders/loader-es.js b/scripts/template/loader/index-es.js similarity index 100% rename from scripts/loaders/loader-es.js rename to scripts/template/loader/index-es.js diff --git a/scripts/loaders/loader.js b/scripts/template/loader/index.js similarity index 100% rename from scripts/loaders/loader.js rename to scripts/template/loader/index.js diff --git a/scripts/template/utils/index-es.js b/scripts/template/utils/index-es.js new file mode 100644 index 0000000..1f213f1 --- /dev/null +++ b/scripts/template/utils/index-es.js @@ -0,0 +1,24 @@ +import languageList from './language-list'; + +/** + * 通过 extname 配合 filename 获取对应的语言 + * @param {string} extname + * @param {string} filename + */ +export const getLanguageIdByExtnameAndFilename = (extname, filename) => { + // 先用 filename 去查找 + let result = languageList.find(language => Array.isArray(language.filenames) && language.filenames.includes(filename)); + if (!result) { + result = languageList.find(language => Array.isArray(language.extensions) && language.extensions.includes(extname)); + } + return result; +}; + +/** + * 是否存在对应的 language id + * @param {string} languageId + * @returns boolean + */ +export const hasLanguageId = (languageId) => { + return languageList.findIndex(language => language.id === languageId) > -1; +} diff --git a/scripts/template/utils/index.js b/scripts/template/utils/index.js new file mode 100644 index 0000000..64c8823 --- /dev/null +++ b/scripts/template/utils/index.js @@ -0,0 +1,24 @@ +const languageList = require('./language-list'); + +/** + * 通过 extname 配合 filename 获取对应的语言 + * @param {string} extname + * @param {string} filename + */ +exports.getLanguageIdByExtnameAndFilename = (extname, filename) => { + // 先用 filename 去查找 + let result = languageList.find(language => Array.isArray(language.filenames) && language.filenames.includes(filename)); + if (!result) { + result = languageList.find(language => Array.isArray(language.extensions) && language.extensions.includes(extname)); + } + return result; +}; + +/** + * 是否存在对应的 language id + * @param {string} languageId + * @returns boolean + */ +exports.hasLanguageId = (languageId) => { + return languageList.findIndex(language => language.id === languageId) > -1; +}