├── .gitignore ├── .npmignore ├── test ├── .babelrc ├── locales │ ├── zh_CN.js │ └── en_US.js └── index.js ├── src ├── log.js ├── config.js ├── key.js ├── helper.js ├── Cache.js └── index.js ├── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | lib 3 | node_modules 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .babelrc 2 | .idea 3 | lib 4 | test 5 | node_modules 6 | -------------------------------------------------------------------------------- /test/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "../src/" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /test/locales/zh_CN.js: -------------------------------------------------------------------------------- 1 | export default { 2 | "hello": "你好", 3 | world: "世界" 4 | } 5 | -------------------------------------------------------------------------------- /test/locales/en_US.js: -------------------------------------------------------------------------------- 1 | export default { 2 | "hello": "Hello", 3 | world: "world" 4 | } 5 | -------------------------------------------------------------------------------- /src/log.js: -------------------------------------------------------------------------------- 1 | module.exports = function() { 2 | console.log('\n-----------------------'); 3 | console.log(arguments); 4 | console.log('-----------------------\n'); 5 | }; 6 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | console.log(intl.get('hello')); 2 | console.log(intl.get('world')); 3 | console.log(intl.get('hustcc')); 4 | 5 | // this will be error because of 1st parameter of intl.get is not string constant. 6 | // console.log(intl.get(`${'hello'}`)); 7 | -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(config) { 2 | return Object.assign({}, { 3 | localeFiles: ['zh_CN', 'en_US'], 4 | i18nFunction: { 5 | object: 'intl', 6 | property: 'get' 7 | }, 8 | uniquePrefix: '$', 9 | }, config); 10 | }; 11 | -------------------------------------------------------------------------------- /src/key.js: -------------------------------------------------------------------------------- 1 | const shortUniqueId = require('short-unique-string'); 2 | const Cache = require('./Cache.js'); 3 | 4 | // 新建一个 cache 5 | const cache = new Cache(); 6 | 7 | const getUniqueId = shortUniqueId({ 8 | number: true, 9 | }); 10 | /** 11 | * 生成新的短文案 12 | * @param key 13 | * @param prefix 14 | * @returns {*} 15 | */ 16 | exports.getShortKey = function (key, prefix) { 17 | if (key.indexOf(prefix) === 0) return key; 18 | if (!cache.has(key)) cache.set(key, prefix + getUniqueId()); 19 | 20 | return cache.get(key); 21 | }; 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "babel-plugin-optimize-i18n", 3 | "version": "0.1.0", 4 | "description": "babel plugin for intl locales optimize. saving 40% ~ 50% bundle size", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "test": "rm -rf lib && babel test -d lib" 8 | }, 9 | "keywords": [ 10 | "i18n-optimize", 11 | "babel-plugin" 12 | ], 13 | "dependencies": { 14 | "short-unique-string": "^1.0.0" 15 | }, 16 | "devDependencies": { 17 | "babel-cli": "^6.26.0" 18 | }, 19 | "author": "hustcc", 20 | "license": "MIT" 21 | } 22 | -------------------------------------------------------------------------------- /src/helper.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | /** 4 | * 只要有一个配置文件复合,则复合 5 | * @param localeFiles 6 | * @param file 7 | * @returns {boolean} 8 | */ 9 | exports.isLocaleFile = function (localeFiles, file) { 10 | const fo = path.parse(file); 11 | return localeFiles.some(lf => fo.name === lf); 12 | }; 13 | 14 | /** 15 | * 是否为 locale content 16 | * @param path 17 | * @returns {boolean} 18 | */ 19 | exports.isLocaleFileContent = function (path) { 20 | const properties = path.get('properties'); 21 | return properties.every(property => { 22 | const propertyPath = property.get('key'); 23 | 24 | return propertyPath.isStringLiteral() || propertyPath.isIdentifier(); 25 | }); 26 | }; 27 | -------------------------------------------------------------------------------- /src/Cache.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 变化 key 的时候,存储的 map 4 | * key: 实际的 key 5 | * value: 变换之后的 key 6 | */ 7 | function Cache() { 8 | this.map = {}; 9 | } 10 | 11 | Cache.prototype.has = function (key) { 12 | return this.map[key] !== undefined; 13 | }; 14 | 15 | Cache.prototype.get = function (key) { 16 | return this.map[key]; 17 | }; 18 | 19 | Cache.prototype.set = function (key, value) { 20 | this.map[key] = value; 21 | }; 22 | 23 | Cache.prototype.getKey = function (value) { 24 | const keys = Object.keys(this.map); 25 | 26 | for (let i = 0; i < keys.length; i ++) { 27 | const key = keys[i]; 28 | if (this.map[key] === value) return key; 29 | } 30 | }; 31 | 32 | Cache.prototype.size = function () { 33 | return Object.keys(this.map).length; 34 | }; 35 | 36 | module.exports = Cache; 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # babel-plugin-optimize-i18n 2 | 3 | A babel plugin for optimizing i18n locales file which can reduce 40% ~ 50% bundle size. 4 | 5 | [![npm](https://img.shields.io/npm/v/babel-plugin-optimize-i18n.svg)](https://www.npmjs.com/package/babel-plugin-optimize-i18n) 6 | [![npm download](https://img.shields.io/npm/dm/babel-plugin-optimize-i18n.svg)](https://www.npmjs.com/package/babel-plugin-optimize-i18n) 7 | 8 | 9 | ## Install 10 | 11 | 12 | > npm i --save-dev babel-plugin-optimize-i18n 13 | 14 | 15 | 16 | ## Usage 17 | 18 | 19 | Add it into `.babelrc`. 20 | 21 | ```json 22 | { 23 | "plugins": [ 24 | "optimize-i18n" 25 | ] 26 | } 27 | ``` 28 | 29 | 30 | 31 | ## Result 32 | 33 | 34 | The source i18n text file below: 35 | 36 | ```json 37 | { 38 | "module.left.title": "左侧标题", 39 | "module.right.title": "右侧标题" 40 | } 41 | ``` 42 | 43 | will be transformed to: 44 | 45 | ```js 46 | { 47 | $1: "左侧标题", 48 | $2: "右侧标题" 49 | } 50 | ``` 51 | 52 | 53 | 54 | ## Configure 55 | 56 | 57 | You can customize the configure of the plugin. 58 | 59 | ```js 60 | { 61 | localeFiles: ['zh_CN', 'en_US'], 62 | i18nFunction: { 63 | object: 'intl', 64 | property: 'get' 65 | }, 66 | uniquePrefix: '$', 67 | } 68 | ``` 69 | 70 | 71 | 72 | ## Test 73 | 74 | 75 | ``` 76 | npm i 77 | 78 | npm t 79 | ``` 80 | 81 | Then see the files in `lib` dir. 82 | 83 | 84 | 85 | ## Constraint 86 | 87 | 88 | For example, when you use the function below to format i18n text. 89 | 90 | ```js 91 | intl.get('key', params); 92 | ``` 93 | 94 | You should be sure the 1st parameter of `intl.get` is string constant, or will be build error. 95 | 96 | 97 | 98 | ## License 99 | 100 | MIT@[hustcc](https://github.com/hustcc). 101 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 主要是为了压缩 locale 文案大小,减少文案包大小 40% ~ 50%。 3 | * 4 | * 约定:按照最佳实践来安排文案目录。 5 | * - 每个语言一个文件,文件格式:[A-Za-z]{2}_[A-Za-z]{2}.js|ts 6 | * - 约定文案格式化方法为 intl.get(key, obj) 其中 intl 可以自定义 7 | * 8 | * 处理: 9 | * - 将 文案 js 对象中的 key 使用 short key 转换,并且映射关系缓存起来 10 | * - 然后将 intl.get(key, obj) 中的 key 使用 short key 转换,并且映射关系缓存起来 11 | * - 如果 short key 已经存在,就直接使用,否则就重新创建 12 | */ 13 | const helper = require('./helper.js'); 14 | const log = require('./log.js'); 15 | const config = require('./config.js'); 16 | const shortKey = require('./key.js'); 17 | 18 | 19 | module.exports = function({ types: babelTypes }) { 20 | return { 21 | name: 'babel-plugin-optimize-i18n', 22 | pre(state) {}, 23 | visitor: { 24 | /** 25 | * 将 object 中的 key 变成短地址 26 | */ 27 | ObjectExpression(path, state) { 28 | const filenameRelative = state.file.opts.filenameRelative; 29 | const pluginConfig = config(state.opts); 30 | // 如果是目标文件 31 | if ( 32 | helper.isLocaleFile(pluginConfig.localeFiles, filenameRelative) && 33 | helper.isLocaleFileContent(path) 34 | ) { 35 | const properties = path.get('properties'); 36 | // 遍历处理 37 | properties.forEach(property => { 38 | const propertyPath = property.get('key'); 39 | 40 | const key = propertyPath.isStringLiteral() ? propertyPath.node.value : propertyPath.node.name; 41 | const k = shortKey.getShortKey(key, pluginConfig.uniquePrefix); 42 | // 如果不相等,则替换 43 | if (k !== key) { 44 | propertyPath.replaceWith( 45 | babelTypes.identifier(k) 46 | ); 47 | } 48 | }) 49 | } 50 | }, 51 | /** 52 | * 将 intl.get(key, opt) 中的 key 替换成短文案 53 | * @param path 54 | * @param state 55 | * @constructor 56 | */ 57 | CallExpression(path, state) { 58 | const object = path.get('callee').get('object'); 59 | const property = path.get('callee').get('property'); 60 | 61 | if (object.isIdentifier() && property.isIdentifier()) { 62 | const pluginConfig = config(state.opts); 63 | // 匹配到 intl.get 方法 64 | // 1. 校验第一个参数是不是字符串 65 | // 2. 如果是,则替换,不是则 throw 66 | if (object.node.name === pluginConfig.i18nFunction.object && 67 | property.node.name === pluginConfig.i18nFunction.property) { 68 | const argument = path.get('arguments.0'); 69 | if (argument.isStringLiteral()) { 70 | const key = argument.node.value; 71 | const k = shortKey.getShortKey(key, pluginConfig.uniquePrefix); 72 | // 如果不相等,则替换 73 | if (k !== key) { 74 | path.node.arguments[0].value = k; 75 | } 76 | } else { 77 | throw path.buildCodeFrameError(`The first parameter of ${pluginConfig.i18nFunction.object}.${pluginConfig.i18nFunction.property} should be a string constant!`); 78 | } 79 | } 80 | } 81 | }, 82 | }, 83 | post(state) { 84 | } 85 | }; 86 | }; 87 | --------------------------------------------------------------------------------