From b9b14522e392d73e41f5d13d0019f5f1deaa830c Mon Sep 17 00:00:00 2001 From: kekee000 Date: Tue, 27 Jan 2015 18:15:30 +0800 Subject: [PATCH] add ttf node module auto build --- build-node.sh | 6 ++ edp-build-config-node.js | 73 +++++++++++++++ node/amd2module.js | 186 +++++++++++++++++++++++++++++++++++++++ node/generated.js | 35 ++++++++ node/test.js | 13 +++ package.json | 2 + src/common/DOMParser.js | 15 ++++ src/ttf/svg2ttfobject.js | 3 +- test/china.svg | 73 +++++++++++++++ 9 files changed, 405 insertions(+), 1 deletion(-) create mode 100644 build-node.sh create mode 100644 edp-build-config-node.js create mode 100644 node/amd2module.js create mode 100644 node/generated.js create mode 100644 node/test.js create mode 100644 src/common/DOMParser.js create mode 100644 test/china.svg diff --git a/build-node.sh b/build-node.sh new file mode 100644 index 0000000..dcbcd5f --- /dev/null +++ b/build-node.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +# ttf 模块node端组件导出程序 + +edp build --config ./edp-build-config-node.js --force +echo "asset path:../fonteditor-ttf/lib" diff --git a/edp-build-config-node.js b/edp-build-config-node.js new file mode 100644 index 0000000..1eb49c1 --- /dev/null +++ b/edp-build-config-node.js @@ -0,0 +1,73 @@ +var path = require( 'path' ); +var amd2module = require('./node/amd2module'); + +exports.input = path.resolve(__dirname, './src');; +exports.output = path.resolve(__dirname, '../fonteditor-ttf/lib'); + + +exports.getProcessors = function () { + + var NodeModuleProcessor = { + name: 'NodeModuleProcessor', + files: [ + 'common/lang.js', + 'common/string.js', + 'common/observable.js', + 'common/DomParser.js', + 'math/**/*.js', + 'graphics/**/*.js', + 'ttf/**/*.js' + ], + process: function(file, processContext, callback) { + + var depth = new Array(file.path.split('/').length).join('../'); + + if (file.data.indexOf('define') >= 0) { + file.setData(amd2module(file.data, depth)); + } + callback(); + } + } + + return [ + NodeModuleProcessor, + // 清除冗余文件,比如`less` + new OutputCleaner({ + files: [ + 'common/ajaxFile.js', + 'common/DataStore.js', + 'common/observable.js', + 'common/promise.js', + 'editor/**', + 'fonteditor/**', + 'render/**', + 'template/**' + ] + }) + ]; +}; + +exports.exclude = [ + ".svn", + "*.conf", + "*.sh", + "*.bat", + "*.md", + "demo", + "agent/*", + "mock", + "test/*", + "edp-*", + "output", + ".DS_Store", + ".gitignore", + "package.json", + "node" +]; + +exports.injectProcessor = function ( processors ) { + for ( var key in processors ) { + global[ key ] = processors[ key ]; + } +}; + diff --git a/node/amd2module.js b/node/amd2module.js new file mode 100644 index 0000000..0b25c05 --- /dev/null +++ b/node/amd2module.js @@ -0,0 +1,186 @@ +/** + * @file amd 模块转换为commonjs模块 + * @author mengke01(kekee000@gmail.com) + */ + +var esprima = require('esprima'); +var estraverse = require( 'estraverse' ); +var escodegen = require('escodegen'); +var SYNTAX = estraverse.Syntax; + +// 顶级模块,用来生成相对位置 +var REG_TOP_MODULE = /^(:?common|math|graphics|ttf)/; + +var EXPORT_SEGMENT = { + "expression": { + "left": { + "computed": false, + "object": { + "name": "module", + "type": "Identifier" + }, + "property": { + "name": "exports", + "type": "Identifier" + }, + "type": "MemberExpression" + }, + "operator": "=", + "right": null, + "type": "AssignmentExpression" + }, + "type": "ExpressionStatement" +}; + +/** + * 获取导出声明 + * + * @return {Object} + */ +function getExportStatement() { + return JSON.parse(JSON.stringify(EXPORT_SEGMENT)); +} + + +/** + * 获取ast树 + * @param {string} code 代码片段 + * @return {Object} ast树 + */ +function getAst(code) { + var ast = null; + + try { + ast = esprima.parse(code, { + }); + } catch (ex) { + throw 'can\'t parse amd code'; + } + + return ast; +} + +/** + * 获取define的factory + * + * @return {astNode} + */ +function getDefineFactory(defineExpr) { + + var args = defineExpr['arguments']; + var factoryAst; + + // 解析参数 + for (var i = 0, l = args.length; i < l; i++) { + var argument = args[i]; + if (argument.type === SYNTAX.FunctionExpression || argument.type === SYNTAX.ObjectExpression) { + factoryAst = argument; + break; + } + } + + return factoryAst; +} + +/** + * 替换define的return为 module.exports + * + * @param {Object} ast ast + * @return {Object} ast + */ +function replaceDefine(ast) { + + estraverse.replace(ast, { + enter: function (node, parent) { + + if ( node.type == SYNTAX.ExpressionStatement + && node.expression.type == SYNTAX.CallExpression + && node.expression.callee.name == 'define' + ) { + var factory = getDefineFactory(node.expression); + // define('xxx', {}) + if (factory.type === SYNTAX.ObjectExpression) { + var exportStatment = getExportStatement(); + exportStatment.expression.right = factory; + + return exportStatment; + } + + // define(function() {}) + else if (factory.type === SYNTAX.FunctionExpression){ + var body = factory.body.body; + // 替换return + for (var i = body.length - 1; i >=0; i--) { + if (body[i].type == SYNTAX.ReturnStatement) { + var exportStatment = getExportStatement(); + exportStatment.expression.right = body[i].argument; + body.splice(i, 1, exportStatment); + break; + } + } + + var index = parent.body.indexOf(node); + Array.prototype.splice.apply(parent.body, [index, 1].concat(body)); + return body[0]; + } + } + + return node; + } + } ); + + return ast; +} + + +/** + * 替换require的绝对路径为相对路径 + * + * @param {Object} ast ast + * @param {Object} codeDepth 当前模块位置 + * @return {Object} ast + */ +function replaceRequire(ast, codeDepth) { + + estraverse.replace(ast, { + enter: function (node, parent) { + + if ( node.type == SYNTAX.CallExpression + && node.callee.name == 'require' + && node.arguments.length + && node.arguments[0].type == 'Literal' + ) { + var argument = node.arguments[0]; + if (REG_TOP_MODULE.test(argument.value)) { + argument.value = codeDepth + argument.value; + } + } + + return node; + } + } ); + + return ast; +} + + +/** + * 生成commonjs代码 + * @param {Object} ast ast树 + * @return {string} 生成后的代码 + */ +function genCommonJS(ast) { + return escodegen.generate(ast); +} + +module.exports = function (code, codeDepth) { + + if (codeDepth && codeDepth[codeDepth.length - 1] !== '/') { + codeDepth += '/'; + } + + var ast = getAst(code); + var replacedAst = replaceRequire(ast, codeDepth); + replacedAst = replaceDefine(replacedAst); + return genCommonJS(replacedAst); +}; diff --git a/node/generated.js b/node/generated.js new file mode 100644 index 0000000..0dd1521 --- /dev/null +++ b/node/generated.js @@ -0,0 +1,35 @@ +var string = require('../common/string'); +var error = { + 10001: '\u8D85\u51FA\u8BFB\u53D6\u8303\u56F4\uFF1A${0}, ${1}', + 10002: '\u8D85\u51FA\u5199\u5165\u8303\u56F4\uFF1A${0}, ${1}', + 10003: '\u672A\u77E5\u6570\u636E\u7C7B\u578B\uFF1A${0}, ${1}', + 10004: '\u4E0D\u652F\u6301svg\u89E3\u6790', + 10101: '\u9519\u8BEF\u7684ttf\u6587\u4EF6', + 10102: '\u9519\u8BEF\u7684woff\u6587\u4EF6', + 10103: '\u9519\u8BEF\u7684svg\u6587\u4EF6', + 10104: '\u8BFB\u53D6ttf\u6587\u4EF6\u9519\u8BEF', + 10105: '\u8BFB\u53D6woff\u6587\u4EF6\u9519\u8BEF', + 10106: '\u8BFB\u53D6svg\u6587\u4EF6\u9519\u8BEF', + 10107: '\u5199\u5165ttf\u6587\u4EF6\u9519\u8BEF', + 10108: '\u5199\u5165woff\u6587\u4EF6\u9519\u8BEF', + 10109: '\u5199\u5165svg\u6587\u4EF6\u9519\u8BEF', + 10110: '\u8BFB\u53D6eot\u6587\u4EF6\u9519\u8BEF', + 10111: '\u8BFB\u53D6eot\u5B57\u4F53\u9519\u8BEF', + 10200: '\u91CD\u590D\u7684unicode\u4EE3\u7801\u70B9\uFF0C\u5B57\u5F62\u5E8F\u53F7\uFF1A${0}', + 10201: '\u5B57\u5F62\u8F6E\u5ED3\u6570\u636E\u4E3A\u7A7A', + 10202: '\u4E0D\u652F\u6301\u6807\u5FD7\u4F4D\uFF1AARGS_ARE_XY_VALUES', + 10203: '\u672A\u627E\u5230\u8868\uFF1A${0}', + 10204: '\u8BFB\u53D6\u8868\u9519\u8BEF', + 10205: '\u672A\u627E\u5230\u89E3\u538B\u51FD\u6570' +}; +error.raise = function (number) { + var message = error[number]; + if (arguments.length > 1) { + var args = typeof arguments[1] === 'object' ? arguments[1] : Array.prototype.slice.call(arguments, 1); + message = string.format(message, args); + } + var e = new Error(message); + e.number = number; + throw e; +}; +module.exports = error; \ No newline at end of file diff --git a/node/test.js b/node/test.js new file mode 100644 index 0000000..9099f31 --- /dev/null +++ b/node/test.js @@ -0,0 +1,13 @@ +/** + * @file 测试模块转换 + * @author mengke01(kekee000@gmail.com) + */ +var fs = require('fs'); +var amd2module = require('./amd2module'); + +function main() { + var code = fs.readFileSync('../src/ttf/error.js'); + fs.writeFileSync('./generated.js', amd2module(code, '../')); +} + +main(); diff --git a/package.json b/package.json index 64c92be..b053b44 100644 --- a/package.json +++ b/package.json @@ -20,5 +20,7 @@ "author": "", "license": "MIT", "devDependencies": { + "esprima": "~1.1.1", + "estraverse": "~1.5.0", } } diff --git a/src/common/DOMParser.js b/src/common/DOMParser.js new file mode 100644 index 0000000..97b9d6f --- /dev/null +++ b/src/common/DOMParser.js @@ -0,0 +1,15 @@ +/** + * @file DOM解析器,兼容node端和浏览器端 + * @author mengke01(kekee000@gmail.com) + */ + +if (typeof exports !== 'undefined') { + module.exports = exports = require('xmldom').DOMParser; +} +else { + define( + function (require) { + return window.DOMParser; + } + ); +} diff --git a/src/ttf/svg2ttfobject.js b/src/ttf/svg2ttfobject.js index 36e9319..8ffb456 100644 --- a/src/ttf/svg2ttfobject.js +++ b/src/ttf/svg2ttfobject.js @@ -10,6 +10,7 @@ define( function (require) { var string = require('common/string'); + var DOMParser = require('common/DOMParser'); var path2contours = require('./svg/path2contours'); var svgnode2contours = require('./svg/svgnode2contours'); var contoursTransform = require('./svg/contoursTransform'); @@ -24,7 +25,7 @@ define( * @return {XMLDocument} */ function loadXML(xml) { - if (document.implementation && document.implementation.createDocument) { + if (DOMParser) { try { var domParser = new DOMParser(); var xmlDoc = domParser.parseFromString(xml, 'text/xml'); diff --git a/test/china.svg b/test/china.svg new file mode 100644 index 0000000..fa39700 --- /dev/null +++ b/test/china.svg @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +