├── cli ├── bin │ └── index.js ├── utils │ ├── log.js │ ├── mergeIi8nConfig.js │ ├── autoi18n.config.js │ ├── localeFile.js │ └── baseUtils.js ├── command │ ├── restore.js │ ├── collect.js │ └── initFileConf.js └── index.js ├── .DS_Store ├── core ├── index.js ├── utils │ ├── cacheCommentHtml.js │ ├── cacheI18nField.js │ ├── cacheCommentJs.js │ └── baseUtils.js ├── restore │ └── index.js └── transform │ ├── index.js │ ├── transformReact.js │ ├── transformJs.js │ ├── transform.js │ ├── transformVue.js │ └── ast.js ├── examples ├── react-autoi18n-cli │ ├── public │ │ ├── robots.txt │ │ ├── favicon.ico │ │ ├── logo192.png │ │ ├── logo512.png │ │ ├── manifest.json │ │ └── index.html │ ├── src │ │ ├── locales │ │ │ ├── zh-cn.json │ │ │ └── en-us.json │ │ ├── setupTests.js │ │ ├── index.css │ │ ├── reportWebVitals.js │ │ ├── index.js │ │ ├── i18n.js │ │ ├── App.css │ │ ├── App.js │ │ └── logo.svg │ ├── config │ │ ├── jest │ │ │ ├── cssTransform.js │ │ │ ├── babelTransform.js │ │ │ └── fileTransform.js │ │ ├── pnpTs.js │ │ ├── getHttpsConfig.js │ │ ├── paths.js │ │ ├── modules.js │ │ ├── env.js │ │ └── webpackDevServer.config.js │ ├── .gitignore │ ├── autoi18n.config.js │ ├── scripts │ │ ├── test.js │ │ ├── start.js │ │ └── build.js │ ├── README.md │ └── package.json ├── react-autoi18n-loaders │ ├── public │ │ ├── robots.txt │ │ ├── favicon.ico │ │ ├── logo192.png │ │ ├── logo512.png │ │ ├── manifest.json │ │ └── index.html │ ├── src │ │ ├── locales │ │ │ ├── zh-cn.json │ │ │ └── en-us.json │ │ ├── setupTests.js │ │ ├── index.css │ │ ├── reportWebVitals.js │ │ ├── index.js │ │ ├── i18n.js │ │ ├── App.css │ │ ├── App.js │ │ └── logo.svg │ ├── config │ │ ├── jest │ │ │ ├── cssTransform.js │ │ │ ├── babelTransform.js │ │ │ └── fileTransform.js │ │ ├── pnpTs.js │ │ ├── getHttpsConfig.js │ │ ├── paths.js │ │ ├── modules.js │ │ ├── env.js │ │ └── webpackDevServer.config.js │ ├── .gitignore │ ├── autoi18n.config.js │ ├── scripts │ │ ├── test.js │ │ ├── start.js │ │ └── build.js │ ├── README.md │ └── package.json ├── vue-autoi18n-cli │ ├── babel.config.js │ ├── public │ │ ├── favicon.ico │ │ └── index.html │ ├── src │ │ ├── assets │ │ │ └── logo.png │ │ ├── locales │ │ │ ├── zh-cn.json │ │ │ └── en-us.json │ │ ├── main.js │ │ ├── i18n.js │ │ ├── App.vue │ │ └── components │ │ │ └── HelloWorld.vue │ ├── .gitignore │ ├── README.md │ ├── autoi18n.config.js │ ├── .prettierrc.js │ ├── package.json │ └── vue.config.js └── vue-autoi18n-loaders │ ├── babel.config.js │ ├── public │ ├── favicon.ico │ └── index.html │ ├── src │ ├── assets │ │ └── logo.png │ ├── locales │ │ ├── zh-cn.json │ │ └── en-us.json │ ├── main.js │ ├── i18n.js │ └── App.vue │ ├── .gitignore │ ├── README.md │ ├── autoi18n.config.js │ ├── .prettierrc.js │ ├── vue.config.js │ └── package.json ├── loaders └── index.js ├── .gitignore ├── package.json ├── README.md └── autoi8nScheme.md /cli/bin/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require('..') -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaneyxs/autoi18n/HEAD/.DS_Store -------------------------------------------------------------------------------- /core/index.js: -------------------------------------------------------------------------------- 1 | exports.transform = require('./transform/index') 2 | exports.restore = require('./restore/index') 3 | -------------------------------------------------------------------------------- /examples/react-autoi18n-cli/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /examples/react-autoi18n-loaders/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /examples/vue-autoi18n-cli/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /examples/vue-autoi18n-loaders/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /examples/vue-autoi18n-cli/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaneyxs/autoi18n/HEAD/examples/vue-autoi18n-cli/public/favicon.ico -------------------------------------------------------------------------------- /examples/vue-autoi18n-cli/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaneyxs/autoi18n/HEAD/examples/vue-autoi18n-cli/src/assets/logo.png -------------------------------------------------------------------------------- /examples/react-autoi18n-cli/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaneyxs/autoi18n/HEAD/examples/react-autoi18n-cli/public/favicon.ico -------------------------------------------------------------------------------- /examples/react-autoi18n-cli/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaneyxs/autoi18n/HEAD/examples/react-autoi18n-cli/public/logo192.png -------------------------------------------------------------------------------- /examples/react-autoi18n-cli/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaneyxs/autoi18n/HEAD/examples/react-autoi18n-cli/public/logo512.png -------------------------------------------------------------------------------- /examples/react-autoi18n-loaders/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaneyxs/autoi18n/HEAD/examples/react-autoi18n-loaders/public/favicon.ico -------------------------------------------------------------------------------- /examples/react-autoi18n-loaders/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaneyxs/autoi18n/HEAD/examples/react-autoi18n-loaders/public/logo192.png -------------------------------------------------------------------------------- /examples/react-autoi18n-loaders/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaneyxs/autoi18n/HEAD/examples/react-autoi18n-loaders/public/logo512.png -------------------------------------------------------------------------------- /examples/vue-autoi18n-loaders/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaneyxs/autoi18n/HEAD/examples/vue-autoi18n-loaders/public/favicon.ico -------------------------------------------------------------------------------- /examples/vue-autoi18n-loaders/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaneyxs/autoi18n/HEAD/examples/vue-autoi18n-loaders/src/assets/logo.png -------------------------------------------------------------------------------- /examples/react-autoi18n-cli/src/locales/zh-cn.json: -------------------------------------------------------------------------------- 1 | { 2 | "1457a8cf081b42e8461e210209b9661c": "欢迎使用 autoi18n", 3 | "a7bac2239fcdcb3a067903d8077c4a07": "中文", 4 | "f9fb6a063d1856da86a06def2dc6b921": "英文" 5 | } -------------------------------------------------------------------------------- /examples/vue-autoi18n-cli/src/locales/zh-cn.json: -------------------------------------------------------------------------------- 1 | { 2 | "1457a8cf081b42e8461e210209b9661c": "欢迎使用 autoi18n", 3 | "a7bac2239fcdcb3a067903d8077c4a07": "中文", 4 | "f9fb6a063d1856da86a06def2dc6b921": "英文" 5 | } -------------------------------------------------------------------------------- /examples/vue-autoi18n-loaders/src/locales/zh-cn.json: -------------------------------------------------------------------------------- 1 | { 2 | "1457a8cf081b42e8461e210209b9661c": "欢迎使用 autoi18n", 3 | "a7bac2239fcdcb3a067903d8077c4a07": "中文", 4 | "f9fb6a063d1856da86a06def2dc6b921": "英文" 5 | } -------------------------------------------------------------------------------- /examples/react-autoi18n-loaders/src/locales/zh-cn.json: -------------------------------------------------------------------------------- 1 | { 2 | "1457a8cf081b42e8461e210209b9661c": "欢迎使用 autoi18n", 3 | "a7bac2239fcdcb3a067903d8077c4a07": "中文", 4 | "f9fb6a063d1856da86a06def2dc6b921": "英文" 5 | } -------------------------------------------------------------------------------- /examples/react-autoi18n-cli/src/locales/en-us.json: -------------------------------------------------------------------------------- 1 | { 2 | "1457a8cf081b42e8461e210209b9661c": "Welcome to use autoi18n", 3 | "a7bac2239fcdcb3a067903d8077c4a07": "Chinese", 4 | "f9fb6a063d1856da86a06def2dc6b921": "English" 5 | } -------------------------------------------------------------------------------- /examples/vue-autoi18n-cli/src/locales/en-us.json: -------------------------------------------------------------------------------- 1 | { 2 | "1457a8cf081b42e8461e210209b9661c": "Welcome to use autoi18n", 3 | "a7bac2239fcdcb3a067903d8077c4a07": "Chinese", 4 | "f9fb6a063d1856da86a06def2dc6b921": "English" 5 | } -------------------------------------------------------------------------------- /examples/vue-autoi18n-loaders/src/locales/en-us.json: -------------------------------------------------------------------------------- 1 | { 2 | "1457a8cf081b42e8461e210209b9661c": "Welcome to use autoi18n", 3 | "a7bac2239fcdcb3a067903d8077c4a07": "Chinese", 4 | "f9fb6a063d1856da86a06def2dc6b921": "English" 5 | } -------------------------------------------------------------------------------- /examples/react-autoi18n-loaders/src/locales/en-us.json: -------------------------------------------------------------------------------- 1 | { 2 | "1457a8cf081b42e8461e210209b9661c": "Welcome to use autoi18n", 3 | "a7bac2239fcdcb3a067903d8077c4a07": "Chinese", 4 | "f9fb6a063d1856da86a06def2dc6b921": "English" 5 | } -------------------------------------------------------------------------------- /examples/vue-autoi18n-cli/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import i18n from '@/i18n' 4 | Vue.config.productionTip = false 5 | 6 | new Vue({ 7 | i18n, 8 | render: (h) => h(App), 9 | }).$mount('#app') 10 | -------------------------------------------------------------------------------- /examples/vue-autoi18n-loaders/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import i18n from '@/i18n' 4 | Vue.config.productionTip = false 5 | 6 | new Vue({ 7 | i18n, 8 | render: (h) => h(App), 9 | }).$mount('#app') 10 | -------------------------------------------------------------------------------- /cli/utils/log.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk'); 2 | 3 | module.exports = { 4 | info: msg => console.log(chalk.cyan(msg)), 5 | warning: msg => console.log(chalk.yellow(msg)), 6 | success: msg => console.log(chalk.green(msg)), 7 | error: msg => console.log(chalk.red(msg)), 8 | }; 9 | -------------------------------------------------------------------------------- /examples/react-autoi18n-cli/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import "@testing-library/jest-dom"; 6 | -------------------------------------------------------------------------------- /examples/react-autoi18n-loaders/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import "@testing-library/jest-dom"; 6 | -------------------------------------------------------------------------------- /examples/vue-autoi18n-cli/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | -------------------------------------------------------------------------------- /examples/vue-autoi18n-loaders/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | -------------------------------------------------------------------------------- /examples/react-autoi18n-cli/config/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // This is a custom Jest transformer turning style imports into empty objects. 4 | // http://facebook.github.io/jest/docs/en/webpack.html 5 | 6 | module.exports = { 7 | process() { 8 | return 'module.exports = {};'; 9 | }, 10 | getCacheKey() { 11 | // The output is always the same. 12 | return 'cssTransform'; 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /examples/react-autoi18n-loaders/config/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // This is a custom Jest transformer turning style imports into empty objects. 4 | // http://facebook.github.io/jest/docs/en/webpack.html 5 | 6 | module.exports = { 7 | process() { 8 | return 'module.exports = {};'; 9 | }, 10 | getCacheKey() { 11 | // The output is always the same. 12 | return 'cssTransform'; 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /examples/react-autoi18n-cli/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /examples/react-autoi18n-loaders/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /examples/react-autoi18n-cli/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /examples/react-autoi18n-loaders/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /examples/react-autoi18n-cli/src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = (onPerfEntry) => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import("web-vitals").then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /examples/vue-autoi18n-cli/README.md: -------------------------------------------------------------------------------- 1 | # vue-autoi18n 2 | 3 | ## Project setup 4 | ``` 5 | npm install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | npm run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | npm run build 16 | ``` 17 | 18 | ### Lints and fixes files 19 | ``` 20 | npm run lint 21 | ``` 22 | 23 | ### Customize configuration 24 | See [Configuration Reference](https://cli.vuejs.org/config/). 25 | -------------------------------------------------------------------------------- /examples/react-autoi18n-loaders/src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = (onPerfEntry) => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import("web-vitals").then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /examples/vue-autoi18n-loaders/README.md: -------------------------------------------------------------------------------- 1 | # vue-autoi18n 2 | 3 | ## Project setup 4 | ``` 5 | npm install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | npm run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | npm run build 16 | ``` 17 | 18 | ### Lints and fixes files 19 | ``` 20 | npm run lint 21 | ``` 22 | 23 | ### Customize configuration 24 | See [Configuration Reference](https://cli.vuejs.org/config/). 25 | -------------------------------------------------------------------------------- /examples/vue-autoi18n-cli/src/i18n.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueI18n from 'vue-i18n' 3 | 4 | Vue.use(VueI18n) 5 | 6 | const messages = {} 7 | const files = require.context('./locales', true, /\.json$/) 8 | 9 | files.keys().forEach((key) => { 10 | const name = key.replace(/^\.\/(.*)\.\w+$/, '$1') 11 | messages[name] = files(key) 12 | }) 13 | 14 | const i18n = new VueI18n({ 15 | locale: 'zh-cn', 16 | fallbackLocale: 'zh-cn', 17 | messages, 18 | }) 19 | 20 | export default i18n 21 | -------------------------------------------------------------------------------- /examples/vue-autoi18n-loaders/src/i18n.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueI18n from 'vue-i18n' 3 | 4 | Vue.use(VueI18n) 5 | 6 | const messages = {} 7 | const files = require.context('./locales', true, /\.json$/) 8 | 9 | files.keys().forEach((key) => { 10 | const name = key.replace(/^\.\/(.*)\.\w+$/, '$1') 11 | messages[name] = files(key) 12 | }) 13 | 14 | const i18n = new VueI18n({ 15 | locale: 'zh-cn', 16 | fallbackLocale: 'zh-cn', 17 | messages, 18 | }) 19 | 20 | export default i18n 21 | -------------------------------------------------------------------------------- /examples/react-autoi18n-loaders/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import "./index.css"; 4 | import App from "./App"; 5 | import reportWebVitals from "./reportWebVitals"; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | , 11 | document.getElementById("root") 12 | ); 13 | 14 | // If you want to start measuring performance in your app, pass a function 15 | // to log results (for example: reportWebVitals(console.log)) 16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 17 | reportWebVitals(); 18 | -------------------------------------------------------------------------------- /examples/vue-autoi18n-cli/autoi18n.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | language: ['zh-cn', 'en-us'], 3 | modules: 'es6', 4 | entry: ['./src'], 5 | localePath: './src/locales', 6 | localeFileExt: '.json', 7 | extensions: [], 8 | exclude: ['./src/locales/*.{js,ts,json}'], 9 | ignoreMethods: ['i18n.t', '$t'], 10 | ignoreTagAttr: ['class', 'style', 'src', 'href', 'width', 'height'], 11 | i18nObjectMethod: 'i18n.t', 12 | i18nMethod: '$t', 13 | setMessageKey: false, 14 | i18nInstance: "import i18n from '@/i18n'", 15 | prettier: { singleQuote: true, trailingComma: 'es5', endOfLine: 'lf' }, 16 | }; 17 | -------------------------------------------------------------------------------- /examples/vue-autoi18n-loaders/autoi18n.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | language: ['zh-cn', 'en-us'], 3 | modules: 'es6', 4 | entry: ['./src'], 5 | localePath: './src/locales', 6 | localeFileExt: '.json', 7 | extensions: [], 8 | exclude: ['./src/locales/*.{js,ts,json}'], 9 | ignoreMethods: ['i18n.t', '$t'], 10 | ignoreTagAttr: ['class', 'style', 'src', 'href', 'width', 'height'], 11 | i18nObjectMethod: 'i18n.t', 12 | i18nMethod: '$t', 13 | setMessageKey: false, 14 | i18nInstance: "import i18n from '@/i18n'", 15 | prettier: { singleQuote: true, trailingComma: 'es5', endOfLine: 'lf' }, 16 | }; 17 | -------------------------------------------------------------------------------- /examples/react-autoi18n-cli/autoi18n.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | language: ['zh-cn', 'en-us'], 3 | modules: 'es6', 4 | entry: ['./src'], 5 | localePath: './src/locales', 6 | localeFileExt: '.json', 7 | extensions: [], 8 | exclude: ['./src/locales/*.{js,ts,json}'], 9 | ignoreMethods: ['i18n.get'], 10 | ignoreTagAttr: ['class', 'style', 'src', 'href', 'width', 'height'], 11 | i18nObjectMethod: 'i18n.get', 12 | i18nMethod: 'i18n.get', 13 | setMessageKey: false, 14 | i18nInstance: "import { i18n } from '~/i18n'", 15 | prettier: { singleQuote: true, trailingComma: 'es5', endOfLine: 'lf' }, 16 | }; 17 | -------------------------------------------------------------------------------- /examples/react-autoi18n-loaders/autoi18n.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | language: ['zh-cn', 'en-us'], 3 | modules: 'es6', 4 | entry: ['./src'], 5 | localePath: './src/locales', 6 | localeFileExt: '.json', 7 | extensions: [], 8 | exclude: ['./src/locales/*.{js,ts,json}'], 9 | ignoreMethods: ['i18n.get'], 10 | ignoreTagAttr: ['class', 'style', 'src', 'href', 'width', 'height'], 11 | i18nObjectMethod: 'i18n.get', 12 | i18nMethod: 'i18n.get', 13 | setMessageKey: false, 14 | i18nInstance: "import { i18n } from '~/i18n'", 15 | prettier: { singleQuote: true, trailingComma: 'es5', endOfLine: 'lf' }, 16 | }; 17 | -------------------------------------------------------------------------------- /loaders/index.js: -------------------------------------------------------------------------------- 1 | const loaderUtils = require('loader-utils') 2 | const path = require('path') 3 | const fs = require('fs') 4 | const mergeIi8nConfig = require('../cli/utils/mergeIi8nConfig'); 5 | const { transform } = require('../core/index') 6 | let messages = {} 7 | 8 | module.exports = function (source) { 9 | const configOptions = mergeIi8nConfig() 10 | // const options = loaderUtils.getOptions(this); 11 | let targetFile = { ext: path.extname(this.resourcePath), filePath: this.resourcePath } 12 | source = transform({ code: source, targetFile, options: configOptions, messages }) 13 | messages = {} 14 | return source 15 | } -------------------------------------------------------------------------------- /examples/react-autoi18n-cli/src/index.js: -------------------------------------------------------------------------------- 1 | import { i18n } from "~/i18n"; 2 | import React from "react"; 3 | import ReactDOM from "react-dom"; 4 | import "./index.css"; 5 | import App from "./App"; 6 | import reportWebVitals from "./reportWebVitals"; 7 | 8 | ReactDOM.render( 9 | 10 | 11 | , 12 | document.getElementById("root") 13 | ); 14 | 15 | // If you want to start measuring performance in your app, pass a function 16 | // to log results (for example: reportWebVitals(console.log)) 17 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 18 | reportWebVitals(); 19 | -------------------------------------------------------------------------------- /examples/vue-autoi18n-cli/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // prettier配置 3 | printWidth: 180, // 超过最大值换行 4 | tabWidth: 2, // 缩进字节数 5 | useTabs: false, // 缩进不使用tab,使用空格 6 | semi: false, // 句尾添加分号 7 | singleQuote: true, // 使用单引号代替双引号 8 | proseWrap: 'preserve', // 默认值。因为使用了一些折行敏感型的渲染器 而按照markdown文本样式进行折行 9 | arrowParens: 'always', // (x) => {} 箭头函数参数只有一个时是否要有小括号。always:不省略括号 10 | jsxBracketSameLine: false, // 在jsx中把'>' 是否单独放一行 11 | jsxSingleQuote: false, // 在jsx中使用单引号代替双引号 12 | htmlWhitespaceSensitivity: 'ignore', // 指定HTML文件的全局空格敏感度 13 | endOfLine: 'auto', // 行结束 14 | // trailingComma: 'none' // 无尾随逗号 15 | }; 16 | -------------------------------------------------------------------------------- /examples/react-autoi18n-cli/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /examples/react-autoi18n-cli/src/i18n.js: -------------------------------------------------------------------------------- 1 | import intl from "react-intl-universal"; 2 | 3 | const locales = {}; 4 | const files = require.context("./locales", true, /\.json$/); 5 | 6 | files.keys().forEach((key) => { 7 | const name = key.replace(/^\.\/(.*)\.\w+$/, "$1"); 8 | locales[name] = files(key); 9 | }); 10 | 11 | const currentLocale = window.localStorage.getItem("lang") || "zh-cn"; 12 | intl.init({ 13 | currentLocale, 14 | locales, 15 | }); 16 | 17 | // 切换语言 18 | export const changeLang = (lang) => { 19 | window.localStorage.setItem("lang", lang); 20 | window.location.reload(); 21 | }; 22 | 23 | export const i18n = intl; 24 | -------------------------------------------------------------------------------- /examples/react-autoi18n-loaders/src/i18n.js: -------------------------------------------------------------------------------- 1 | import intl from "react-intl-universal"; 2 | 3 | const locales = {}; 4 | const files = require.context("./locales", true, /\.json$/); 5 | 6 | files.keys().forEach((key) => { 7 | const name = key.replace(/^\.\/(.*)\.\w+$/, "$1"); 8 | locales[name] = files(key); 9 | }); 10 | 11 | const currentLocale = window.localStorage.getItem("lang") || "zh-cn"; 12 | intl.init({ 13 | currentLocale, 14 | locales, 15 | }); 16 | 17 | // 切换语言 18 | export const changeLang = (lang) => { 19 | window.localStorage.setItem("lang", lang); 20 | window.location.reload(); 21 | }; 22 | 23 | export const i18n = intl; 24 | -------------------------------------------------------------------------------- /examples/vue-autoi18n-loaders/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // prettier配置 3 | printWidth: 180, // 超过最大值换行 4 | tabWidth: 2, // 缩进字节数 5 | useTabs: false, // 缩进不使用tab,使用空格 6 | semi: false, // 句尾添加分号 7 | singleQuote: true, // 使用单引号代替双引号 8 | proseWrap: 'preserve', // 默认值。因为使用了一些折行敏感型的渲染器 而按照markdown文本样式进行折行 9 | arrowParens: 'always', // (x) => {} 箭头函数参数只有一个时是否要有小括号。always:不省略括号 10 | jsxBracketSameLine: false, // 在jsx中把'>' 是否单独放一行 11 | jsxSingleQuote: false, // 在jsx中使用单引号代替双引号 12 | htmlWhitespaceSensitivity: 'ignore', // 指定HTML文件的全局空格敏感度 13 | endOfLine: 'auto', // 行结束 14 | // trailingComma: 'none' // 无尾随逗号 15 | }; 16 | -------------------------------------------------------------------------------- /examples/react-autoi18n-loaders/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /examples/react-autoi18n-cli/config/jest/babelTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const babelJest = require('babel-jest'); 4 | 5 | const hasJsxRuntime = (() => { 6 | if (process.env.DISABLE_NEW_JSX_TRANSFORM === 'true') { 7 | return false; 8 | } 9 | 10 | try { 11 | require.resolve('react/jsx-runtime'); 12 | return true; 13 | } catch (e) { 14 | return false; 15 | } 16 | })(); 17 | 18 | module.exports = babelJest.createTransformer({ 19 | presets: [ 20 | [ 21 | require.resolve('babel-preset-react-app'), 22 | { 23 | runtime: hasJsxRuntime ? 'automatic' : 'classic', 24 | }, 25 | ], 26 | ], 27 | babelrc: false, 28 | configFile: false, 29 | }); 30 | -------------------------------------------------------------------------------- /examples/react-autoi18n-loaders/config/jest/babelTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const babelJest = require('babel-jest'); 4 | 5 | const hasJsxRuntime = (() => { 6 | if (process.env.DISABLE_NEW_JSX_TRANSFORM === 'true') { 7 | return false; 8 | } 9 | 10 | try { 11 | require.resolve('react/jsx-runtime'); 12 | return true; 13 | } catch (e) { 14 | return false; 15 | } 16 | })(); 17 | 18 | module.exports = babelJest.createTransformer({ 19 | presets: [ 20 | [ 21 | require.resolve('babel-preset-react-app'), 22 | { 23 | runtime: hasJsxRuntime ? 'automatic' : 'classic', 24 | }, 25 | ], 26 | ], 27 | babelrc: false, 28 | configFile: false, 29 | }); 30 | -------------------------------------------------------------------------------- /examples/vue-autoi18n-cli/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /examples/vue-autoi18n-loaders/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /examples/react-autoi18n-cli/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | background-color: #282c34; 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .App-link { 28 | color: #61dafb; 29 | } 30 | 31 | @keyframes App-logo-spin { 32 | from { 33 | transform: rotate(0deg); 34 | } 35 | to { 36 | transform: rotate(360deg); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /examples/react-autoi18n-loaders/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | background-color: #282c34; 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .App-link { 28 | color: #61dafb; 29 | } 30 | 31 | @keyframes App-logo-spin { 32 | from { 33 | transform: rotate(0deg); 34 | } 35 | to { 36 | transform: rotate(360deg); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /examples/react-autoi18n-cli/config/pnpTs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { resolveModuleName } = require('ts-pnp'); 4 | 5 | exports.resolveModuleName = ( 6 | typescript, 7 | moduleName, 8 | containingFile, 9 | compilerOptions, 10 | resolutionHost 11 | ) => { 12 | return resolveModuleName( 13 | moduleName, 14 | containingFile, 15 | compilerOptions, 16 | resolutionHost, 17 | typescript.resolveModuleName 18 | ); 19 | }; 20 | 21 | exports.resolveTypeReferenceDirective = ( 22 | typescript, 23 | moduleName, 24 | containingFile, 25 | compilerOptions, 26 | resolutionHost 27 | ) => { 28 | return resolveModuleName( 29 | moduleName, 30 | containingFile, 31 | compilerOptions, 32 | resolutionHost, 33 | typescript.resolveTypeReferenceDirective 34 | ); 35 | }; 36 | -------------------------------------------------------------------------------- /examples/react-autoi18n-loaders/config/pnpTs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { resolveModuleName } = require('ts-pnp'); 4 | 5 | exports.resolveModuleName = ( 6 | typescript, 7 | moduleName, 8 | containingFile, 9 | compilerOptions, 10 | resolutionHost 11 | ) => { 12 | return resolveModuleName( 13 | moduleName, 14 | containingFile, 15 | compilerOptions, 16 | resolutionHost, 17 | typescript.resolveModuleName 18 | ); 19 | }; 20 | 21 | exports.resolveTypeReferenceDirective = ( 22 | typescript, 23 | moduleName, 24 | containingFile, 25 | compilerOptions, 26 | resolutionHost 27 | ) => { 28 | return resolveModuleName( 29 | moduleName, 30 | containingFile, 31 | compilerOptions, 32 | resolutionHost, 33 | typescript.resolveTypeReferenceDirective 34 | ); 35 | }; 36 | -------------------------------------------------------------------------------- /core/utils/cacheCommentHtml.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 对html注释缓存 恢复处理 4 | */ 5 | module.exports = { 6 | /** 7 | * 暂存html注释对象 8 | */ 9 | commentsCache: {}, 10 | /** 11 | * 先去掉html的注释 暂存注释 12 | */ 13 | stash(sourceCode, options) { 14 | this.commentsCache = {} 15 | let commentsIndex = 0 16 | sourceCode = sourceCode.replace(//gm, (match, content) => { 17 | let commentsKey = `%%comment_html_${commentsIndex++}%%` 18 | this.commentsCache[commentsKey] = content 19 | return `` 20 | }) 21 | return sourceCode 22 | }, 23 | /** 24 | * 恢复之前删除的注释 25 | */ 26 | restore(sourceCode, options) { 27 | sourceCode = sourceCode.replace(/%%comment_html_\d+%%/gim, (match) => { 28 | return this.commentsCache[match] 29 | }); 30 | this.commentsCache = {} // 清除缓存 31 | return sourceCode 32 | } 33 | } -------------------------------------------------------------------------------- /examples/vue-autoi18n-loaders/src/App.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 30 | 31 | 41 | -------------------------------------------------------------------------------- /cli/utils/mergeIi8nConfig.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | const defaultConfig = require('./autoi18n.config') 4 | const log = require('./log') 5 | 6 | const cwdPath = process.cwd() 7 | 8 | module.exports = function mergeOptions(programOption) { 9 | const options = defaultConfig; 10 | const configFileName = programOption && programOption.config || 'autoi18n.config.js' 11 | 12 | const configFilePath = path.join(cwdPath, configFileName) 13 | // 读取 autoi18n.config.js 中设置的参数,然后并入 options 14 | if (fs.existsSync(configFilePath)) { 15 | let configurationFile = {} 16 | try { 17 | configurationFile = require(configFilePath) 18 | } catch (err) { 19 | log.warning(`请检查 ${configFileName} 配置文件是否正确\n`) 20 | } 21 | 22 | Object.assign(options, configurationFile) 23 | } else { 24 | log.warning(`配置文件 ${configFileName} 不存在\n采用默认配置`) 25 | } 26 | 27 | return options; 28 | }; 29 | -------------------------------------------------------------------------------- /core/restore/index.js: -------------------------------------------------------------------------------- 1 | const baseUtils = require('../utils/baseUtils') 2 | /** 3 | * 恢复不同的文件文案 4 | * @param {*} options.code 源代码 5 | * @param {*} options.targetFile 文件对象 6 | * @param {*} options.options 国际化配置对象 7 | * @param {*} options.messages 国际化字段对象 8 | * @returns code 经过恢复文案的代码 9 | */ 10 | module.exports = function ({ code, targetFile, options, messages }) { 11 | let ignoreMethods = options.ignoreMethods 12 | // 转义字符串 13 | ignoreMethods = ignoreMethods.map((item) => baseUtils.stringRegEscape(item)) 14 | const ident = ignoreMethods.join('|') 15 | code = code.replace(new RegExp(`(${ident})\\((['"\`])((((?!\\2|\\().)+))\\2[^(]*?\\)`, 'gm'), (match, method, sign , key) => { 16 | if (messages[key]) { 17 | return `${sign}${messages[key]}${sign}` 18 | } 19 | return match 20 | }) 21 | // 删除import 实例对象 22 | // code = code.replace(new RegExp(baseUtils.stringRegEscape(options.i18nInstance), 'gm'), '') 23 | return code 24 | } -------------------------------------------------------------------------------- /examples/react-autoi18n-loaders/src/App.js: -------------------------------------------------------------------------------- 1 | import logo from "./logo.svg"; 2 | import "./App.css"; 3 | import { changeLang } from "~/i18n"; 4 | 5 | function App() { 6 | const setLanguage = (e) => { 7 | changeLang(e.target.value) 8 | } 9 | return ( 10 |
11 |
12 | logo 13 |

14 | Edit src/App.js and save to reload. 15 |

16 | 22 | Learn React 23 | 24 |

欢迎使用 autoi18n

25 | 29 |
30 |
31 | ); 32 | } 33 | 34 | export default App; 35 | -------------------------------------------------------------------------------- /examples/vue-autoi18n-cli/src/App.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 30 | 31 | 41 | -------------------------------------------------------------------------------- /core/transform/index.js: -------------------------------------------------------------------------------- 1 | const transformVue = require('./transformVue') 2 | const transformReact = require('./transformReact') 3 | const transformJs = require('./transformJs') 4 | 5 | /** 6 | * 处理不同的文件转换 7 | * @param {*} options.code 源代码 8 | * @param {*} options.targetFile 文件对象 9 | * @param {*} options.options 国际化配置对象 10 | * @param {*} options.messages 国际化字段对象 11 | * @returns code 经过国际化的代码 12 | */ 13 | module.exports = function ({ code, targetFile, options, messages }) { 14 | let data = '' 15 | if (targetFile.ext === '.vue') { 16 | // 处理vue文件 17 | data = transformVue({ code, file: targetFile, ext: targetFile.ext, options, messages }) 18 | } else if (targetFile.ext === '.js' || targetFile.ext === '.ts') { 19 | // 处理js文件 20 | data = transformJs({ code, file: targetFile, ext: targetFile.ext, options, messages }) 21 | } else if (targetFile.ext === '.jsx' || targetFile.ext === '.tsx') { 22 | // 处理react文件 23 | data = transformReact({ code, file: targetFile, ext: targetFile.ext, options, messages }) 24 | } 25 | return data 26 | } 27 | -------------------------------------------------------------------------------- /examples/vue-autoi18n-loaders/vue.config.js: -------------------------------------------------------------------------------- 1 | 2 | const isDev = process.env.DEPLOY_ENV === 'development' // 开发环境 3 | const isProd = process.env.DEPLOY_ENV === 'production' // 生产环境 4 | const isTest = process.env.DEPLOY_ENV === 'test' // 测试环境 5 | const isDeploy = isProd || isTest // 是否是部署环境 6 | 7 | module.exports = { 8 | parallel: false, 9 | productionSourceMap: isDev, 10 | lintOnSave: true, 11 | publicPath: isDeploy ? '' : '', 12 | assetsDir: 'static', 13 | css: { 14 | // 是否使用css分离插件 ExtractTextPlugin 15 | extract: isDeploy, 16 | // 开启 CSS source maps? 17 | sourceMap: false 18 | }, 19 | chainWebpack: (config) => { 20 | // 配置自动国际化loader 无侵入式 21 | config.module 22 | .rule('autoi18n') 23 | .test(/\.(vue|(j|t)sx?)$/) 24 | .pre() // 这个必须加上 优先执行的loader 顺序一定要在use方法前面 否则会报找不到pre方法 25 | .use('autoi18n') 26 | .loader('autoi18n') 27 | .end() 28 | }, 29 | devServer: { 30 | open: true, //是否自动弹出浏览器页面 31 | port: 8089, // 设置端口号 32 | https: false, //是否使用https协议 33 | hotOnly: false, //是否开启热更新 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /examples/vue-autoi18n-cli/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-autoi18n", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "core-js": "^3.6.5", 12 | "vue": "^2.6.11", 13 | "vue-i18n": "^8.25.0" 14 | }, 15 | "devDependencies": { 16 | "@vue/cli-plugin-babel": "~4.5.0", 17 | "@vue/cli-plugin-eslint": "~4.5.0", 18 | "@vue/cli-service": "~4.5.0", 19 | "babel-eslint": "^10.1.0", 20 | "eslint": "^6.7.2", 21 | "eslint-plugin-vue": "^6.2.2", 22 | "vue-template-compiler": "^2.6.11" 23 | }, 24 | "eslintConfig": { 25 | "root": true, 26 | "env": { 27 | "node": true 28 | }, 29 | "extends": [ 30 | "plugin:vue/essential", 31 | "eslint:recommended" 32 | ], 33 | "parserOptions": { 34 | "parser": "babel-eslint" 35 | }, 36 | "rules": {} 37 | }, 38 | "browserslist": [ 39 | "> 1%", 40 | "last 2 versions", 41 | "not dead" 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /examples/vue-autoi18n-loaders/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-autoi18n", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "core-js": "^3.6.5", 12 | "vue": "^2.6.11", 13 | "vue-i18n": "^8.25.0" 14 | }, 15 | "devDependencies": { 16 | "@vue/cli-plugin-babel": "~4.5.0", 17 | "@vue/cli-plugin-eslint": "~4.5.0", 18 | "@vue/cli-service": "~4.5.0", 19 | "autoi18n": "^1.0.6", 20 | "babel-eslint": "^10.1.0", 21 | "eslint": "^6.7.2", 22 | "eslint-plugin-vue": "^6.2.2", 23 | "vue-template-compiler": "^2.6.11" 24 | }, 25 | "eslintConfig": { 26 | "root": true, 27 | "env": { 28 | "node": true 29 | }, 30 | "extends": [ 31 | "plugin:vue/essential", 32 | "eslint:recommended" 33 | ], 34 | "parserOptions": { 35 | "parser": "babel-eslint" 36 | }, 37 | "rules": {} 38 | }, 39 | "browserslist": [ 40 | "> 1%", 41 | "last 2 versions", 42 | "not dead" 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /core/utils/cacheI18nField.js: -------------------------------------------------------------------------------- 1 | const baseUtils = require('./baseUtils') 2 | /** 3 | * 对已设置国际化的字段进行缓存 恢复 4 | * 忽略已经设置了国际化的字符串 防止key为中文时 会重复设置 5 | */ 6 | module.exports = { 7 | /** 8 | * 暂存国际化字符串对象 9 | */ 10 | i18nFieldCache: {}, 11 | /** 12 | * 先去掉国际化字符串 缓存 13 | */ 14 | stash(sourceCode, options) { 15 | this.i18nFieldCache = {} 16 | let i18nFieldIndex = 0 17 | let ignoreMethods = options.ignoreMethods 18 | // 转义字符串 19 | ignoreMethods = ignoreMethods.map((item) => baseUtils.stringRegEscape(item)) 20 | const ident = ignoreMethods.join('|') 21 | sourceCode = sourceCode.replace(new RegExp(`(${ident})\\([^(]+?\\)`, 'gm'), (match) => { 22 | let i18nFieldKey = `__i18n_field_${i18nFieldIndex++}__()` 23 | this.i18nFieldCache[i18nFieldKey] = match 24 | return i18nFieldKey 25 | }) 26 | return sourceCode 27 | }, 28 | /** 29 | * 恢复之前删除的国际化字符串 30 | */ 31 | restore(sourceCode, options) { 32 | sourceCode = sourceCode.replace(/__i18n_field_\d+__\(\)/gm, (match) => { 33 | return this.i18nFieldCache[match] 34 | }); 35 | this.i18nFieldCache = {} // 清除缓存 36 | return sourceCode 37 | } 38 | } -------------------------------------------------------------------------------- /examples/react-autoi18n-cli/src/App.js: -------------------------------------------------------------------------------- 1 | import { i18n } from "~/i18n"; 2 | import logo from "./logo.svg"; 3 | import "./App.css"; 4 | import { changeLang } from "~/i18n"; 5 | 6 | function App() { 7 | const setLanguage = (e) => { 8 | changeLang(e.target.value) 9 | } 10 | return ( 11 |
12 |
13 | logo 14 |

15 | Edit src/App.js and save to reload. 16 |

17 | 23 | Learn React 24 | 25 |

{i18n.get("1457a8cf081b42e8461e210209b9661c")}

26 | 30 |
31 |
32 | ); 33 | } 34 | 35 | export default App; 36 | -------------------------------------------------------------------------------- /core/transform/transformReact.js: -------------------------------------------------------------------------------- 1 | const cacheCommentJs = require('../utils/cacheCommentJs') 2 | const cacheI18nField = require('../utils/cacheI18nField') 3 | const ast = require('./ast') 4 | const baseUtils = require('../utils/baseUtils') 5 | 6 | /** 7 | * 转换react 8 | * @param {*} options.code 源代码 9 | * @param {*} options.options 国际化配置对象 10 | * @param {*} options.file 文件对象 11 | * @param {*} options.messages 国际化字段对象 12 | * @param {*} options.ext 文件类型 13 | * @returns 14 | */ 15 | module.exports = function ({ code, file, options, messages, ext = '.jsx' }) { 16 | // 复制一份国际化数据配置 17 | const oldMessages = JSON.stringify(messages) 18 | // 暂存注释 react 注释 就是js注释 ast替换的是字符串 所以可以不处理注释 19 | // code = cacheCommentJs.stash(code, options) 20 | // 暂存已经设置的国际化字段 21 | code = cacheI18nField.stash(code, options) 22 | // 转换react 23 | const lang = ['.ts', '.tsx'].includes(ext) ? 'ts' : 'js' 24 | code = ast({ code, file, options, messages, ext, codeType: 'jsx', lang }) 25 | // 恢复注释 26 | // code = cacheCommentJs.restore(code, options) 27 | // 恢复已经设置的国际化字段 28 | code = cacheI18nField.restore(code, options) 29 | // 国际化数据发生变化才注入 证明该js有国际化字段 30 | if (oldMessages !== JSON.stringify(messages)) { 31 | // 注入实例 32 | code = baseUtils.injectInstance({ code, ext, options }) 33 | } 34 | return code 35 | } 36 | -------------------------------------------------------------------------------- /core/transform/transformJs.js: -------------------------------------------------------------------------------- 1 | const cacheCommentJs = require('../utils/cacheCommentJs') 2 | const cacheI18nField = require('../utils/cacheI18nField') 3 | const ast = require('./ast') 4 | const baseUtils = require('../utils/baseUtils') 5 | 6 | /** 7 | * 转换js 8 | * @param {*} options.code 源代码 9 | * @param {*} options.file 文件对象 10 | * @param {*} options.options 国际化配置对象 11 | * @param {*} options.messages 国际化字段对象 12 | * @param {*} options.codeType 代码类型 13 | * @param {*} options.ext 文件类型 14 | * @returns 15 | */ 16 | module.exports = function ({ code, file, options, messages, lang, codeType = 'js', ext = '.js' }) { 17 | // 复制一份国际化数据配置 18 | const oldMessages = JSON.stringify(messages) 19 | // 暂存注释 20 | // code = cacheCommentJs.stash(code, options) ast替换的是字符串 所以可以不处理注释 21 | // 暂存已经设置的国际化字段 22 | code = cacheI18nField.stash(code, options) 23 | // 转换js 24 | lang = lang ? lang : ext === '.ts' ? 'ts' : 'js' 25 | code = ast({ code, file, options, messages, ext, codeType, lang }) 26 | // 恢复注释 27 | // code = cacheCommentJs.restore(code, options) 28 | // 恢复已经设置的国际化字段 29 | code = cacheI18nField.restore(code, options) 30 | // 国际化数据发生变化才注入 证明该js有国际化字段 31 | if (oldMessages !== JSON.stringify(messages)) { 32 | // 注入实例 33 | code = baseUtils.injectInstance({ code, ext, options }) 34 | } 35 | return code 36 | } 37 | -------------------------------------------------------------------------------- /examples/react-autoi18n-cli/config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const camelcase = require('camelcase'); 5 | 6 | // This is a custom Jest transformer turning file imports into filenames. 7 | // http://facebook.github.io/jest/docs/en/webpack.html 8 | 9 | module.exports = { 10 | process(src, filename) { 11 | const assetFilename = JSON.stringify(path.basename(filename)); 12 | 13 | if (filename.match(/\.svg$/)) { 14 | // Based on how SVGR generates a component name: 15 | // https://github.com/smooth-code/svgr/blob/01b194cf967347d43d4cbe6b434404731b87cf27/packages/core/src/state.js#L6 16 | const pascalCaseFilename = camelcase(path.parse(filename).name, { 17 | pascalCase: true, 18 | }); 19 | const componentName = `Svg${pascalCaseFilename}`; 20 | return `const React = require('react'); 21 | module.exports = { 22 | __esModule: true, 23 | default: ${assetFilename}, 24 | ReactComponent: React.forwardRef(function ${componentName}(props, ref) { 25 | return { 26 | $$typeof: Symbol.for('react.element'), 27 | type: 'svg', 28 | ref: ref, 29 | key: null, 30 | props: Object.assign({}, props, { 31 | children: ${assetFilename} 32 | }) 33 | }; 34 | }), 35 | };`; 36 | } 37 | 38 | return `module.exports = ${assetFilename};`; 39 | }, 40 | }; 41 | -------------------------------------------------------------------------------- /examples/react-autoi18n-loaders/config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const camelcase = require('camelcase'); 5 | 6 | // This is a custom Jest transformer turning file imports into filenames. 7 | // http://facebook.github.io/jest/docs/en/webpack.html 8 | 9 | module.exports = { 10 | process(src, filename) { 11 | const assetFilename = JSON.stringify(path.basename(filename)); 12 | 13 | if (filename.match(/\.svg$/)) { 14 | // Based on how SVGR generates a component name: 15 | // https://github.com/smooth-code/svgr/blob/01b194cf967347d43d4cbe6b434404731b87cf27/packages/core/src/state.js#L6 16 | const pascalCaseFilename = camelcase(path.parse(filename).name, { 17 | pascalCase: true, 18 | }); 19 | const componentName = `Svg${pascalCaseFilename}`; 20 | return `const React = require('react'); 21 | module.exports = { 22 | __esModule: true, 23 | default: ${assetFilename}, 24 | ReactComponent: React.forwardRef(function ${componentName}(props, ref) { 25 | return { 26 | $$typeof: Symbol.for('react.element'), 27 | type: 'svg', 28 | ref: ref, 29 | key: null, 30 | props: Object.assign({}, props, { 31 | children: ${assetFilename} 32 | }) 33 | }; 34 | }), 35 | };`; 36 | } 37 | 38 | return `module.exports = ${assetFilename};`; 39 | }, 40 | }; 41 | -------------------------------------------------------------------------------- /cli/utils/autoi18n.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | /** 3 | * 需要国际化的语言种类 4 | */ 5 | language: ['zh-cn', 'en-us'], 6 | /** 7 | * 国际化资源文件应用的 模块模式 根据这个模式 使用 module.exports 或者 export default 8 | * 如果localeFileExt 配置为json时 此配置不起效 9 | */ 10 | modules: 'es6', 11 | /** 12 | * 需要国际化的目录 13 | */ 14 | entry: ['./src'], 15 | /** 16 | * 国际化资源文件输出目录 17 | */ 18 | localePath: './src/locales', 19 | /** 20 | * 国际化文件类型 默认 为 .json文件 支持.js和.json 21 | */ 22 | localeFileExt: '.json', 23 | /** 24 | * 需要处理国际化的文件后缀 25 | */ 26 | extensions: [], 27 | /** 28 | * 需要排除国际化的文件 glob模式数组 29 | */ 30 | exclude: [], 31 | /** 32 | * 要忽略做国际化的方法 33 | */ 34 | ignoreMethods: ['i18n.t', '$t'], 35 | /** 36 | * 要忽略做标签属性 37 | */ 38 | ignoreTagAttr: ['class', 'style', 'src', 'href', 'width', 'height'], 39 | /** 40 | * 国际化对象方法,可以自定义使用方法返回 注意:如果改变国际化方法记得把该方法加到ignoreMethods忽略列表里面 41 | */ 42 | i18nObjectMethod: 'i18n.t', 43 | /** 44 | * 国际化方法简写模式,可以自定使用方法返回 注意:如果改变国际化方法记得把该方法加到ignoreMethods忽略列表里面 45 | */ 46 | i18nMethod: '$t', 47 | /** 48 | * 如果不喜欢又臭又长的key 可以自定义国际化配置文件的key 49 | * 默认为 false 不自定义 50 | */ 51 | setMessageKey: false, 52 | /** 53 | * 生成md5的key长度 true: 32位字符 false: 16位字符 54 | */ 55 | maxLenKey: false, 56 | /** 57 | * 国际化要注入到js里面的实例 会在js文件第一行注入 58 | */ 59 | i18nInstance: "import i18n from '~/i18n'", 60 | /** 61 | * 格式化文件配置 62 | */ 63 | prettier: { 64 | singleQuote: true, 65 | trailingComma: 'es5', 66 | endOfLine: 'lf', 67 | } 68 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Node template 3 | # Logs 4 | /logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | *.pid.lock 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | 22 | # nyc test coverage 23 | .nyc_output 24 | 25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 26 | .grunt 27 | 28 | # Bower dependency directory (https://bower.io/) 29 | bower_components 30 | 31 | # node-waf configuration 32 | .lock-wscript 33 | 34 | # Compiled binary addons (https://nodejs.org/api/addons.html) 35 | build/Release 36 | 37 | # Dependency directories 38 | node_modules/ 39 | jspm_packages/ 40 | 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # parcel-bundler cache (https://parceljs.org/) 61 | .cache 62 | 63 | # Nuxt generate 64 | dist 65 | 66 | # vuepress build output 67 | .vuepress/dist 68 | 69 | # Serverless directories 70 | .serverless 71 | 72 | # IDE / Editor 73 | .idea 74 | 75 | # Service worker 76 | sw.* 77 | 78 | # macOS 79 | .DS_Store 80 | 81 | # Vim swap files 82 | *.swp 83 | 84 | package-lock.json 85 | 86 | yarn-lock.json 87 | 88 | dist.zip 89 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "autoi18n-tool", 3 | "version": "1.0.8", 4 | "main": "./loaders/index.js", 5 | "license": "MIT", 6 | "bin": { 7 | "autoi18n": "./cli/bin/index.js" 8 | }, 9 | "keywords": [ 10 | "i18n", 11 | "autoi18n", 12 | "auto-i18n", 13 | "autoi18n-tool" 14 | ], 15 | "repository": { 16 | "url": "https://github.com/Gertyxs/autoi18n/tree/main" 17 | }, 18 | "files": [ 19 | "cli", 20 | "core", 21 | "loaders" 22 | ], 23 | "author": "Gertyxs ", 24 | "scripts": {}, 25 | "dependencies": { 26 | "@babel/core": "^7.14.8", 27 | "@babel/generator": "^7.14.8", 28 | "@babel/plugin-proposal-optional-chaining": "^7.14.5", 29 | "@babel/plugin-syntax-async-generators": "^7.8.4", 30 | "@babel/plugin-syntax-class-properties": "^7.12.13", 31 | "@babel/plugin-syntax-decorators": "^7.14.5", 32 | "@babel/plugin-syntax-do-expressions": "^7.14.5", 33 | "@babel/plugin-syntax-dynamic-import": "^7.8.3", 34 | "@babel/plugin-syntax-export-extensions": "^7.0.0-beta.32", 35 | "@babel/plugin-syntax-function-bind": "^7.14.5", 36 | "@babel/plugin-syntax-jsx": "^7.14.5", 37 | "@babel/plugin-syntax-object-rest-spread": "^7.8.3", 38 | "@babel/preset-typescript": "^7.14.5", 39 | "@babel/traverse": "^7.14.8", 40 | "@babel/types": "^7.14.8", 41 | "chalk": "^4.1.1", 42 | "commander": "^8.0.0", 43 | "crypto-js": "^4.1.1", 44 | "glob": "^7.1.7", 45 | "inquirer": "^8.1.1", 46 | "prettier": "^2.3.2" 47 | }, 48 | "peerDependencies": {}, 49 | "devDependencies": {} 50 | } 51 | -------------------------------------------------------------------------------- /examples/react-autoi18n-cli/scripts/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'test'; 5 | process.env.NODE_ENV = 'test'; 6 | process.env.PUBLIC_URL = ''; 7 | 8 | // Makes the script crash on unhandled rejections instead of silently 9 | // ignoring them. In the future, promise rejections that are not handled will 10 | // terminate the Node.js process with a non-zero exit code. 11 | process.on('unhandledRejection', err => { 12 | throw err; 13 | }); 14 | 15 | // Ensure environment variables are read. 16 | require('../config/env'); 17 | 18 | 19 | const jest = require('jest'); 20 | const execSync = require('child_process').execSync; 21 | let argv = process.argv.slice(2); 22 | 23 | function isInGitRepository() { 24 | try { 25 | execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' }); 26 | return true; 27 | } catch (e) { 28 | return false; 29 | } 30 | } 31 | 32 | function isInMercurialRepository() { 33 | try { 34 | execSync('hg --cwd . root', { stdio: 'ignore' }); 35 | return true; 36 | } catch (e) { 37 | return false; 38 | } 39 | } 40 | 41 | // Watch unless on CI or explicitly running all tests 42 | if ( 43 | !process.env.CI && 44 | argv.indexOf('--watchAll') === -1 && 45 | argv.indexOf('--watchAll=false') === -1 46 | ) { 47 | // https://github.com/facebook/create-react-app/issues/5210 48 | const hasSourceControl = isInGitRepository() || isInMercurialRepository(); 49 | argv.push(hasSourceControl ? '--watch' : '--watchAll'); 50 | } 51 | 52 | 53 | jest.run(argv); 54 | -------------------------------------------------------------------------------- /examples/react-autoi18n-loaders/scripts/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'test'; 5 | process.env.NODE_ENV = 'test'; 6 | process.env.PUBLIC_URL = ''; 7 | 8 | // Makes the script crash on unhandled rejections instead of silently 9 | // ignoring them. In the future, promise rejections that are not handled will 10 | // terminate the Node.js process with a non-zero exit code. 11 | process.on('unhandledRejection', err => { 12 | throw err; 13 | }); 14 | 15 | // Ensure environment variables are read. 16 | require('../config/env'); 17 | 18 | 19 | const jest = require('jest'); 20 | const execSync = require('child_process').execSync; 21 | let argv = process.argv.slice(2); 22 | 23 | function isInGitRepository() { 24 | try { 25 | execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' }); 26 | return true; 27 | } catch (e) { 28 | return false; 29 | } 30 | } 31 | 32 | function isInMercurialRepository() { 33 | try { 34 | execSync('hg --cwd . root', { stdio: 'ignore' }); 35 | return true; 36 | } catch (e) { 37 | return false; 38 | } 39 | } 40 | 41 | // Watch unless on CI or explicitly running all tests 42 | if ( 43 | !process.env.CI && 44 | argv.indexOf('--watchAll') === -1 && 45 | argv.indexOf('--watchAll=false') === -1 46 | ) { 47 | // https://github.com/facebook/create-react-app/issues/5210 48 | const hasSourceControl = isInGitRepository() || isInMercurialRepository(); 49 | argv.push(hasSourceControl ? '--watch' : '--watchAll'); 50 | } 51 | 52 | 53 | jest.run(argv); 54 | -------------------------------------------------------------------------------- /cli/command/restore.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const mergeIi8nConfig = require('../utils/mergeIi8nConfig') 3 | const prettier = require('prettier') 4 | const log = require('../utils/log') 5 | const baseUtils = require('../utils/baseUtils') 6 | const { restore } = require('../../core/index') 7 | const LocaleFile = require('../utils/localeFile') 8 | 9 | /** 10 | * 恢复国际化字段为对应文案 11 | * @param {*} programOption 命令行参数 12 | */ 13 | module.exports = async function (programOption) { 14 | // 合并配置文件 15 | const options = mergeIi8nConfig(programOption) 16 | // 国际化配置文件路径 17 | const firstLocalePath = options.language && options.language[0] ? options.language[0] : 'zh-cn' 18 | 19 | // 没有指定国际化文件配置 20 | if (!firstLocalePath && !programOption.file) { 21 | log.error('Internationalization configuration file not found'); 22 | process.exit(2); 23 | } 24 | 25 | // 创建生成国际化文件对象 26 | const localeFile = new LocaleFile(options.localePath) 27 | 28 | // 国际化配置数据 29 | let messages = {} 30 | if (programOption.file) { 31 | messages = localeFile.getConf(firstLocalePath, options, programOption.file) 32 | } else { 33 | messages = localeFile.getConf(firstLocalePath, options) 34 | } 35 | 36 | // 获取所有入口文件路劲 37 | let targetFiles = baseUtils.getSourceFiles(options) 38 | // 开始读取文件进行操作 39 | for (let i = 0; i < targetFiles.length; i++) { 40 | const sourceCode = fs.readFileSync(targetFiles[i].filePath, 'utf8'); 41 | let code = restore({ code: sourceCode, targetFile: targetFiles[i], options, messages }) 42 | code = prettier.format(code, baseUtils.getPrettierOptions(targetFiles[i].ext, options)) 43 | fs.writeFileSync(targetFiles[i].filePath, code, { encoding: 'utf-8' }) 44 | log.success(`done: ${targetFiles[i].filePath}`) 45 | } 46 | } -------------------------------------------------------------------------------- /examples/react-autoi18n-cli/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /examples/react-autoi18n-loaders/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /cli/command/collect.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const mergeIi8nConfig = require('../utils/mergeIi8nConfig') 3 | const prettier = require('prettier') 4 | const log = require('../utils/log') 5 | const baseUtils = require('../utils/baseUtils') 6 | const { transform } = require('../../core/index') 7 | const LocaleFile = require('../utils/localeFile') 8 | 9 | /** 10 | * 同步国际化配置文件并替换为对应的国际化字段 11 | * @param {*} programOption 命令行参数 12 | */ 13 | module.exports = async function (programOption) { 14 | // 合并配置文件 15 | const options = mergeIi8nConfig(programOption) 16 | 17 | // 指定目录类型错误 18 | if (!Array.isArray(options.entry) && typeof options.entry !== 'string') { 19 | log.error('entry must be a string or array'); 20 | process.exit(2); 21 | } 22 | 23 | // 没有指定国际化目录 24 | if (!options.entry || Array.isArray(options.entry) && options.entry.length <= 0) { 25 | log.error('no entry is specified'); 26 | process.exit(2); 27 | } 28 | 29 | // 国际化配置数据 30 | const messages = {} 31 | 32 | // 获取所有入口文件路劲 33 | let targetFiles = baseUtils.getSourceFiles(options) 34 | // 开始读取文件进行操作 35 | for (let i = 0; i < targetFiles.length; i++) { 36 | const sourceCode = fs.readFileSync(targetFiles[i].filePath, 'utf8'); 37 | let code = transform({ code: sourceCode, targetFile: targetFiles[i], options, messages }) 38 | if (programOption.replace) { 39 | code = prettier.format(code, baseUtils.getPrettierOptions(targetFiles[i].ext, options)) 40 | fs.writeFileSync(targetFiles[i].filePath, code, { encoding: 'utf-8' }) 41 | } 42 | log.success(`done: ${targetFiles[i].filePath}`) 43 | } 44 | 45 | // 创建生成国际化文件对象 46 | const localeFile = new LocaleFile(options.localePath) 47 | // 生成配置文件 48 | createTasks = options.language.map(locale => { 49 | let data = localeFile.getConf(locale, options) 50 | data = baseUtils.mergeMessages(data, messages) 51 | return localeFile.createConf(data, locale, options) 52 | }) 53 | log.success('生成国际化配置文件完成') 54 | 55 | } -------------------------------------------------------------------------------- /cli/index.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const program = require('commander') 3 | const initFileConf = require('./command/initFileConf') 4 | const collect = require('./command/collect') 5 | const package = require('../package') 6 | const restore = require('./command/restore') 7 | 8 | // 用法 版本说明 9 | program 10 | .version(package.version) // 定义版本 11 | .usage('') // 定义用法 12 | 13 | // 初始化配置文件 14 | program 15 | .command('init') // 定义命令 16 | .alias('i') // 命令别名 17 | .description('init locales conf') // 对命令参数的描述信息 18 | .action(function (options) { 19 | initFileConf(options) 20 | }) 21 | .on('--help', function () { 22 | console.log(' Examples:') 23 | console.log(' $ autoi18n init') 24 | }) 25 | 26 | // 同步国际化配置文件并替换为对应的国际化字段 27 | program 28 | .command('sync') // 定义命令 29 | .alias('s') // 命令别名 30 | .description('Synchronize the Chinese configuration to the internationalization profile') // 对命令参数的描述信息 31 | .option('-r, --replace', 'Replace Internationalization Fields') // 替换国际化字段 如果为true 会写入源文件 默认为false 32 | .option('-c, --config ', 'set config path. defaults to ./autoi18n.config.js') // 指定配置文件 33 | .action(function (options) { 34 | collect(options) 35 | }) 36 | .on('--help', function () { 37 | console.log(' Examples:'); 38 | console.log(' $ autoi18n sync') 39 | }) 40 | 41 | // 恢复国际化字段为对应文案 42 | program 43 | .command('restore') // 定义命令 44 | .alias('r') // 命令别名 45 | .description('Restore the internationalized field to the corresponding text') // 对命令参数的描述信息 46 | .option('-c, --config ', 'set config path. defaults to ./autoi18n.config.js') // 指定配置文件 47 | .option('-f, --file ', 'Internationalization configuration file path') // 国际化配置文件路径 默认会获取 language的第一个文件配置数据 48 | .action(function (options) { 49 | restore(options) 50 | }) 51 | .on('--help', function () { 52 | console.log(' Examples:') 53 | console.log(' $ autoi18n restore -f ./src/locales/zh-cn.ts') 54 | }) 55 | 56 | program.parse(process.argv) 57 | -------------------------------------------------------------------------------- /examples/react-autoi18n-cli/config/getHttpsConfig.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const crypto = require('crypto'); 6 | const chalk = require('react-dev-utils/chalk'); 7 | const paths = require('./paths'); 8 | 9 | // Ensure the certificate and key provided are valid and if not 10 | // throw an easy to debug error 11 | function validateKeyAndCerts({ cert, key, keyFile, crtFile }) { 12 | let encrypted; 13 | try { 14 | // publicEncrypt will throw an error with an invalid cert 15 | encrypted = crypto.publicEncrypt(cert, Buffer.from('test')); 16 | } catch (err) { 17 | throw new Error( 18 | `The certificate "${chalk.yellow(crtFile)}" is invalid.\n${err.message}` 19 | ); 20 | } 21 | 22 | try { 23 | // privateDecrypt will throw an error with an invalid key 24 | crypto.privateDecrypt(key, encrypted); 25 | } catch (err) { 26 | throw new Error( 27 | `The certificate key "${chalk.yellow(keyFile)}" is invalid.\n${ 28 | err.message 29 | }` 30 | ); 31 | } 32 | } 33 | 34 | // Read file and throw an error if it doesn't exist 35 | function readEnvFile(file, type) { 36 | if (!fs.existsSync(file)) { 37 | throw new Error( 38 | `You specified ${chalk.cyan( 39 | type 40 | )} in your env, but the file "${chalk.yellow(file)}" can't be found.` 41 | ); 42 | } 43 | return fs.readFileSync(file); 44 | } 45 | 46 | // Get the https config 47 | // Return cert files if provided in env, otherwise just true or false 48 | function getHttpsConfig() { 49 | const { SSL_CRT_FILE, SSL_KEY_FILE, HTTPS } = process.env; 50 | const isHttps = HTTPS === 'true'; 51 | 52 | if (isHttps && SSL_CRT_FILE && SSL_KEY_FILE) { 53 | const crtFile = path.resolve(paths.appPath, SSL_CRT_FILE); 54 | const keyFile = path.resolve(paths.appPath, SSL_KEY_FILE); 55 | const config = { 56 | cert: readEnvFile(crtFile, 'SSL_CRT_FILE'), 57 | key: readEnvFile(keyFile, 'SSL_KEY_FILE'), 58 | }; 59 | 60 | validateKeyAndCerts({ ...config, keyFile, crtFile }); 61 | return config; 62 | } 63 | return isHttps; 64 | } 65 | 66 | module.exports = getHttpsConfig; 67 | -------------------------------------------------------------------------------- /examples/react-autoi18n-loaders/config/getHttpsConfig.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const crypto = require('crypto'); 6 | const chalk = require('react-dev-utils/chalk'); 7 | const paths = require('./paths'); 8 | 9 | // Ensure the certificate and key provided are valid and if not 10 | // throw an easy to debug error 11 | function validateKeyAndCerts({ cert, key, keyFile, crtFile }) { 12 | let encrypted; 13 | try { 14 | // publicEncrypt will throw an error with an invalid cert 15 | encrypted = crypto.publicEncrypt(cert, Buffer.from('test')); 16 | } catch (err) { 17 | throw new Error( 18 | `The certificate "${chalk.yellow(crtFile)}" is invalid.\n${err.message}` 19 | ); 20 | } 21 | 22 | try { 23 | // privateDecrypt will throw an error with an invalid key 24 | crypto.privateDecrypt(key, encrypted); 25 | } catch (err) { 26 | throw new Error( 27 | `The certificate key "${chalk.yellow(keyFile)}" is invalid.\n${ 28 | err.message 29 | }` 30 | ); 31 | } 32 | } 33 | 34 | // Read file and throw an error if it doesn't exist 35 | function readEnvFile(file, type) { 36 | if (!fs.existsSync(file)) { 37 | throw new Error( 38 | `You specified ${chalk.cyan( 39 | type 40 | )} in your env, but the file "${chalk.yellow(file)}" can't be found.` 41 | ); 42 | } 43 | return fs.readFileSync(file); 44 | } 45 | 46 | // Get the https config 47 | // Return cert files if provided in env, otherwise just true or false 48 | function getHttpsConfig() { 49 | const { SSL_CRT_FILE, SSL_KEY_FILE, HTTPS } = process.env; 50 | const isHttps = HTTPS === 'true'; 51 | 52 | if (isHttps && SSL_CRT_FILE && SSL_KEY_FILE) { 53 | const crtFile = path.resolve(paths.appPath, SSL_CRT_FILE); 54 | const keyFile = path.resolve(paths.appPath, SSL_KEY_FILE); 55 | const config = { 56 | cert: readEnvFile(crtFile, 'SSL_CRT_FILE'), 57 | key: readEnvFile(keyFile, 'SSL_KEY_FILE'), 58 | }; 59 | 60 | validateKeyAndCerts({ ...config, keyFile, crtFile }); 61 | return config; 62 | } 63 | return isHttps; 64 | } 65 | 66 | module.exports = getHttpsConfig; 67 | -------------------------------------------------------------------------------- /core/utils/cacheCommentJs.js: -------------------------------------------------------------------------------- 1 | const baseUtils = require('./baseUtils') 2 | /** 3 | * 对js注释缓存 恢复处理 4 | */ 5 | module.exports = { 6 | /** 7 | * 暂存Js注释对象 8 | */ 9 | commentsCache: {}, 10 | /** 11 | * 先去掉js的注释 暂存注释 12 | */ 13 | stash(sourceCode, options) { 14 | this.commentsCache = {} 15 | let commentsIndex = 0 16 | sourceCode = sourceCode.replace(/(\/(\/.*))|(\/\*([\s\S]*?)\*\/)/g, (match, comment1, content1, comment2, content2, offset, string) => { 17 | let comment = '' 18 | let content = '' 19 | // 单行注释 防止匹配到url协议部分 类似 http:// 20 | if (comment1 != undefined && comment1 != null && comment1.length > 0) { 21 | const len = '//'.length 22 | if (offset == 0) { 23 | // 匹配字符串起始就是 //,所以整行都是注释 24 | comment = comment1 25 | content = content1 26 | } 27 | // 获取当前字符串中第一个纯正的单选注释 // 28 | let idxSlash = 0; 29 | while ((idxSlash = comment1.indexOf('//', idxSlash)) >= 0) { 30 | let prefix = string.charAt(offset + idxSlash - 1) 31 | if (prefix === ':') { 32 | // 前一个字符是':',所以不是单行注释 33 | idxSlash = idxSlash + len 34 | continue 35 | } else { 36 | // 拿出注释 37 | comment = comment1.substring(idxSlash, comment1.length) 38 | content = comment1.substring(idxSlash + len, comment1.length) 39 | break 40 | } 41 | } 42 | } 43 | // 多行注释 44 | if (comment2 !== undefined && comment2 !== null && comment2.length > 0) { 45 | comment = comment2 46 | content = content2 47 | } 48 | // 如果存在注释 49 | if (comment && content) { 50 | let commentsKey = `%%comment_js_${commentsIndex++}%%` 51 | this.commentsCache[commentsKey] = content 52 | return match.replace(content, commentsKey) 53 | } else { 54 | return match 55 | } 56 | }) 57 | return sourceCode 58 | }, 59 | /** 60 | * 恢复之前删除的注释 61 | */ 62 | restore(sourceCode, options) { 63 | sourceCode = sourceCode.replace(/%%comment_js_\d+%%/gim, (match) => { 64 | return this.commentsCache[match] 65 | }); 66 | this.commentsCache = {} // 清除缓存 67 | return sourceCode 68 | } 69 | } -------------------------------------------------------------------------------- /core/transform/transform.js: -------------------------------------------------------------------------------- 1 | const { md5, formatWhitespace } = require('../utils/baseUtils') 2 | 3 | /** 4 | * 设置替换 5 | * @param {*} code 6 | */ 7 | const replaceStatement = ({ value, options, messages, ext, codeType, sign = "'" }) => { 8 | // 去掉首尾空白字符,中间的连续空白字符替换成一个空格 9 | value = formatWhitespace(value) 10 | // 生成key 11 | let key = md5(value, options.maxLenKey) 12 | // 是否自定义key 13 | if (options.setMessageKey && typeof options.setMessageKey === 'function') { 14 | key = options.setMessageKey({ key, value }) 15 | } 16 | messages[key] = value 17 | let i18nMethod = null 18 | // 类型为vue标签采用缩写国际化方法的形式 19 | if (codeType === 'vueTag') { 20 | i18nMethod = options.i18nMethod 21 | } else { 22 | // 其余情况使用对象的方法 23 | i18nMethod = options.i18nObjectMethod 24 | } 25 | // 如果是函数 26 | if (i18nMethod && typeof i18nMethod !== 'string') { 27 | return i18nMethod({ key, value, options, ext, sign }) 28 | } 29 | return `${i18nMethod}(${sign}${key}${sign})` 30 | } 31 | 32 | /** 33 | * 匹配字符串模块 34 | * @param {*} code 35 | */ 36 | const matchStringTpl = ({ code, options, messages, codeType, ext }) => { 37 | // 匹配存在中文的字符串模板内容 38 | code = code.replace(/(`)(((?!\1).)*[\u4e00-\u9fa5]+((?!\1).)*)\1/g, (match, sign, value) => { 39 | // 匹配占位符外面的内容 40 | const outValues = value 41 | .replace('`', '') 42 | .replace(/(\${)([^}]+)(})/gm, ',,') 43 | .split(',,') 44 | .filter(item => item) 45 | outValues.forEach(item => { 46 | value = value.replace(item, value => { 47 | // 是否是中文 48 | if (/[\u4e00-\u9fa5]+/g.test(value)) { 49 | value = `\${'${value}'}` 50 | } 51 | return value 52 | }) 53 | }) 54 | return `${sign}${value}${sign}` 55 | }) 56 | return code 57 | } 58 | 59 | /** 60 | * 匹配普通字符串 61 | * @param {*} code 62 | */ 63 | const matchString = ({ code, options, messages, ext, codeType }) => { 64 | // 替换所有包含中文的普通字符串 65 | code = code.replace(/(['"])(((?!\1).)*[\u4e00-\u9fa5]+((?!\1).)*)\1/gm, (match, sign, value) => { 66 | return replaceStatement({ value, options, messages, ext, codeType, sign }) 67 | }) 68 | return code 69 | } 70 | 71 | module.exports = { 72 | matchStringTpl, 73 | matchString, 74 | replaceStatement 75 | } 76 | -------------------------------------------------------------------------------- /examples/vue-autoi18n-cli/src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 43 | 44 | 60 | -------------------------------------------------------------------------------- /examples/vue-autoi18n-cli/vue.config.js: -------------------------------------------------------------------------------- 1 | 2 | const isDev = process.env.DEPLOY_ENV === 'development' // 开发环境 3 | const isProd = process.env.DEPLOY_ENV === 'production' // 生产环境 4 | const isTest = process.env.DEPLOY_ENV === 'test' // 测试环境 5 | const isDeploy = isProd || isTest // 是否是部署环境 6 | 7 | module.exports = { 8 | parallel: false, 9 | productionSourceMap: isDev, 10 | lintOnSave: true, 11 | publicPath: isDeploy ? '' : '', 12 | assetsDir: 'static', 13 | css: { 14 | // 是否使用css分离插件 ExtractTextPlugin 15 | extract: isDeploy, 16 | // 开启 CSS source maps? 17 | sourceMap: false 18 | }, 19 | chainWebpack: (config) => { 20 | if (isProd) { 21 | // 压缩去除console等信息 22 | config.optimization.minimizer('terser').tap((args) => { 23 | Object.assign(args[0].terserOptions.compress, { 24 | // warnings: false , // 默认 false 25 | // drop_console: false, // 默认 26 | // drop_debugger: true, // 去掉 debugger 默认也是true 27 | pure_funcs: ['console.log'] 28 | }) 29 | return args 30 | }) 31 | } 32 | if (isTest) { 33 | config.optimization.minimizer('terser').tap((args) => { 34 | Object.assign(args[0].terserOptions.compress, { 35 | // warnings: false , // 默认 false 36 | // drop_console: false, // 默认 37 | // drop_debugger: true, // 去掉 debugger 默认也是true 38 | // pure_funcs: ['console.log'] 39 | }) 40 | return args 41 | }) 42 | } 43 | }, 44 | devServer: { 45 | open: true, //是否自动弹出浏览器页面 46 | port: 8089, // 设置端口号 47 | https: false, //是否使用https协议 48 | hotOnly: false, //是否开启热更新 49 | proxy: { 50 | // 互金API代理 51 | '/cashier/api': { 52 | // '/cashier-uat/api/': { 53 | // target: 'http://172.16.1.204:9001', // 水平电脑 54 | // target: 'http://172.16.1.103:9001', // 曼婷电脑 55 | // target: 'http://172.16.1.63:9001', // 智文电脑 56 | target: 'https://kwgmb-test.kwgproperty.com', // 测试环境 57 | ws: true, // 代理websockets 58 | changeOrigin: true // 是否跨域,虚拟的站点需要更管origin 59 | // pathRewrite: { 60 | // '/cashier/api': '' 61 | // } 62 | }, 63 | // APP API 代理 64 | '/kwgdspappapi/v2': { 65 | target: 'https://kwgmb-test.kwgproperty.com', // 测试环境 66 | ws: true, // 代理websockets 67 | changeOrigin: true // 是否跨域,虚拟的站点需要更管origin 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /examples/react-autoi18n-cli/src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/react-autoi18n-loaders/src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/react-autoi18n-cli/config/paths.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | const getPublicUrlOrPath = require('react-dev-utils/getPublicUrlOrPath'); 6 | 7 | // Make sure any symlinks in the project folder are resolved: 8 | // https://github.com/facebook/create-react-app/issues/637 9 | const appDirectory = fs.realpathSync(process.cwd()); 10 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath); 11 | 12 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer 13 | // "public path" at which the app is served. 14 | // webpack needs to know it to put the right 131 | ``` 132 | 133 | 在`vue`单文件组织中可以看出我们只要处理`template`以及`script`里面的代码即可 134 | 135 | 对于`vue`单文件组件我们可以使用官方提供的解析库[vue-template-compiler](https://github.com/vuejs/vue/tree/dev/packages/vue-template-compiler#readme)提取`template`和`JavaScript` 136 | 137 | ```js 138 | const compiler = require('vue-template-compiler') 139 | const sfc = compiler.parseComponent(code, { pad: 'space', deindent: false }) 140 | const { template, script, styles, customBlocks } = sfc 141 | ``` 142 | 143 | 上面就把`vue`单文件组织分成了`template`和`JavaScript`了。 144 | 145 | **template** 146 | 147 | 对于`template`我们只需要处理标签的静态属性、动态属性和标签内容即可,处理这些字符我们可以使用[parse5](https://www.npmjs.com/package/parse5)去解析标签或者用`RegExp`匹配,在[autoi8n](https://github.com/Gertyxs/autoi18n)中我使用`RegExp`匹配,如有需要后期会采用标签解析器处理。 148 | 149 | ```js 150 | const matchTagAttr = ({ code }) => { 151 | code = code.replace(/(<[^\/\s]+)([^<>]+)(\/?>)/gm, (match, startTag, attrs, endTag) => { 152 | return code 153 | }) 154 | return code 155 | } 156 | const matchTagContent = ({ code, options, ext, codeType, messages }) => { 157 | code = code.replace(/(>)([^><]*[\u4e00-\u9fa5]+[^><]*)(<)/gm, (match, beforeSign, value, afterSign) => { 158 | return code 159 | }) 160 | return code 161 | } 162 | ``` 163 | 164 | **JavaScript** 165 | 166 | 在上面我们已经讲解过`JavaScript`的处理了,在这里不再敖述。 167 | 168 | #### 3.3 React jsx中的字符 169 | 170 | ```jsx 171 | const render = () => { 172 | return ( 173 |
174 |
175 | {'我是react内容'} 176 | 我是标签静态内容 177 |
178 |
179 | ) 180 | } 181 | ``` 182 | 183 | 在`react`中,我们同样处理`JavaScript`字符串,`jsx`标签属性和标签内容,由于`jsx`本身支持[bable ast](https://astexplorer.net/)解析所以我们直接使用`ast`进行解析。 184 | 185 | **代码分析完成** 186 | 187 | 通过上面对js、vue、JavaScript、的代码分析,我们已经匹配到所有需要做国际化的字符,我们再把对应的字符抽出生成一个messages对象写进文件就完成了资源文件的生成了。然后把对应的字符替换成我们国际化设置的方法就大功告成了。 188 | 189 | #### 3.4 webpack loader 实现无侵入式的自动国国际化 190 | 191 | 实现了对代码处理之后,还可以在webpack loader里面把源码传进来进行无侵入式处理 192 | 193 | ```js 194 | const path = require('path') 195 | const fs = require('fs') 196 | const mergeIi8nConfig = require('../cli/utils/mergeIi8nConfig'); 197 | const { transform } = require('../core/index') 198 | let messages = {} 199 | 200 | module.exports = function (source) { 201 | const configOptions = mergeIi8nConfig() 202 | let targetFile = { ext: path.extname(this.resourcePath), filePath: this.resourcePath } 203 | source = transform({ code: source, targetFile, options: configOptions, messages }) 204 | messages = {} 205 | return source 206 | } 207 | ``` 208 | 209 | webpack loader 配置 210 | 211 | ```js 212 | { 213 | enforce: 'pre', // 此项一定要加上 优先执行的loader 214 | test: /\.(js|mjs|jsx|ts|tsx)$/, 215 | use: [ 216 | { 217 | loader: 'autoi18n', 218 | options: {} 219 | }], 220 | exclude: /node_modules/ 221 | } 222 | ``` 223 | 224 | 225 | 226 | ## 总结 227 | 228 | 整篇文章主要围绕怎么实现项目国际化自动化,希望本文可以给你带来国际化自动化的思路,总的来说国际化自动化就是处理代码中的字符串,可以通过代码解析器或者正则去匹配对应的字符,然后抽取出来替换成对应的国际化key,感谢你的阅读。 229 | 230 | 231 | 232 | 分享一个国际化自动化的库[autoi18n](https://github.com/Gertyxs/autoi18n) 233 | 234 | 项目还在完善中,欢迎大家pr,如果你觉得不错也欢迎给个start 😄😄😄 -------------------------------------------------------------------------------- /examples/react-autoi18n-cli/config/webpackDevServer.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const errorOverlayMiddleware = require('react-dev-utils/errorOverlayMiddleware'); 5 | const evalSourceMapMiddleware = require('react-dev-utils/evalSourceMapMiddleware'); 6 | const noopServiceWorkerMiddleware = require('react-dev-utils/noopServiceWorkerMiddleware'); 7 | const ignoredFiles = require('react-dev-utils/ignoredFiles'); 8 | const redirectServedPath = require('react-dev-utils/redirectServedPathMiddleware'); 9 | const paths = require('./paths'); 10 | const getHttpsConfig = require('./getHttpsConfig'); 11 | 12 | const host = process.env.HOST || '0.0.0.0'; 13 | const sockHost = process.env.WDS_SOCKET_HOST; 14 | const sockPath = process.env.WDS_SOCKET_PATH; // default: '/sockjs-node' 15 | const sockPort = process.env.WDS_SOCKET_PORT; 16 | 17 | module.exports = function (proxy, allowedHost) { 18 | return { 19 | // WebpackDevServer 2.4.3 introduced a security fix that prevents remote 20 | // websites from potentially accessing local content through DNS rebinding: 21 | // https://github.com/webpack/webpack-dev-server/issues/887 22 | // https://medium.com/webpack/webpack-dev-server-middleware-security-issues-1489d950874a 23 | // However, it made several existing use cases such as development in cloud 24 | // environment or subdomains in development significantly more complicated: 25 | // https://github.com/facebook/create-react-app/issues/2271 26 | // https://github.com/facebook/create-react-app/issues/2233 27 | // While we're investigating better solutions, for now we will take a 28 | // compromise. Since our WDS configuration only serves files in the `public` 29 | // folder we won't consider accessing them a vulnerability. However, if you 30 | // use the `proxy` feature, it gets more dangerous because it can expose 31 | // remote code execution vulnerabilities in backends like Django and Rails. 32 | // So we will disable the host check normally, but enable it if you have 33 | // specified the `proxy` setting. Finally, we let you override it if you 34 | // really know what you're doing with a special environment variable. 35 | disableHostCheck: 36 | !proxy || process.env.DANGEROUSLY_DISABLE_HOST_CHECK === 'true', 37 | // Enable gzip compression of generated files. 38 | compress: true, 39 | // Silence WebpackDevServer's own logs since they're generally not useful. 40 | // It will still show compile warnings and errors with this setting. 41 | clientLogLevel: 'none', 42 | // By default WebpackDevServer serves physical files from current directory 43 | // in addition to all the virtual build products that it serves from memory. 44 | // This is confusing because those files won’t automatically be available in 45 | // production build folder unless we copy them. However, copying the whole 46 | // project directory is dangerous because we may expose sensitive files. 47 | // Instead, we establish a convention that only files in `public` directory 48 | // get served. Our build script will copy `public` into the `build` folder. 49 | // In `index.html`, you can get URL of `public` folder with %PUBLIC_URL%: 50 | // 51 | // In JavaScript code, you can access it with `process.env.PUBLIC_URL`. 52 | // Note that we only recommend to use `public` folder as an escape hatch 53 | // for files like `favicon.ico`, `manifest.json`, and libraries that are 54 | // for some reason broken when imported through webpack. If you just want to 55 | // use an image, put it in `src` and `import` it from JavaScript instead. 56 | contentBase: paths.appPublic, 57 | contentBasePublicPath: paths.publicUrlOrPath, 58 | // By default files from `contentBase` will not trigger a page reload. 59 | watchContentBase: true, 60 | // Enable hot reloading server. It will provide WDS_SOCKET_PATH endpoint 61 | // for the WebpackDevServer client so it can learn when the files were 62 | // updated. The WebpackDevServer client is included as an entry point 63 | // in the webpack development configuration. Note that only changes 64 | // to CSS are currently hot reloaded. JS changes will refresh the browser. 65 | hot: true, 66 | // Use 'ws' instead of 'sockjs-node' on server since we're using native 67 | // websockets in `webpackHotDevClient`. 68 | transportMode: 'ws', 69 | // Prevent a WS client from getting injected as we're already including 70 | // `webpackHotDevClient`. 71 | injectClient: false, 72 | // Enable custom sockjs pathname for websocket connection to hot reloading server. 73 | // Enable custom sockjs hostname, pathname and port for websocket connection 74 | // to hot reloading server. 75 | sockHost, 76 | sockPath, 77 | sockPort, 78 | // It is important to tell WebpackDevServer to use the same "publicPath" path as 79 | // we specified in the webpack config. When homepage is '.', default to serving 80 | // from the root. 81 | // remove last slash so user can land on `/test` instead of `/test/` 82 | publicPath: paths.publicUrlOrPath.slice(0, -1), 83 | // WebpackDevServer is noisy by default so we emit custom message instead 84 | // by listening to the compiler events with `compiler.hooks[...].tap` calls above. 85 | quiet: true, 86 | // Reportedly, this avoids CPU overload on some systems. 87 | // https://github.com/facebook/create-react-app/issues/293 88 | // src/node_modules is not ignored to support absolute imports 89 | // https://github.com/facebook/create-react-app/issues/1065 90 | watchOptions: { 91 | ignored: ignoredFiles(paths.appSrc), 92 | }, 93 | https: getHttpsConfig(), 94 | host, 95 | overlay: false, 96 | historyApiFallback: { 97 | // Paths with dots should still use the history fallback. 98 | // See https://github.com/facebook/create-react-app/issues/387. 99 | disableDotRule: true, 100 | index: paths.publicUrlOrPath, 101 | }, 102 | public: allowedHost, 103 | // `proxy` is run between `before` and `after` `webpack-dev-server` hooks 104 | proxy, 105 | before(app, server) { 106 | // Keep `evalSourceMapMiddleware` and `errorOverlayMiddleware` 107 | // middlewares before `redirectServedPath` otherwise will not have any effect 108 | // This lets us fetch source contents from webpack for the error overlay 109 | app.use(evalSourceMapMiddleware(server)); 110 | // This lets us open files from the runtime error overlay. 111 | app.use(errorOverlayMiddleware()); 112 | 113 | if (fs.existsSync(paths.proxySetup)) { 114 | // This registers user provided middleware for proxy reasons 115 | require(paths.proxySetup)(app); 116 | } 117 | }, 118 | after(app) { 119 | // Redirect to `PUBLIC_URL` or `homepage` from `package.json` if url not match 120 | app.use(redirectServedPath(paths.publicUrlOrPath)); 121 | 122 | // This service worker file is effectively a 'no-op' that will reset any 123 | // previous service worker registered for the same host:port combination. 124 | // We do this in development to avoid hitting the production cache if 125 | // it used the same host and port. 126 | // https://github.com/facebook/create-react-app/issues/2272#issuecomment-302832432 127 | app.use(noopServiceWorkerMiddleware(paths.publicUrlOrPath)); 128 | }, 129 | }; 130 | }; 131 | -------------------------------------------------------------------------------- /examples/react-autoi18n-loaders/config/webpackDevServer.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const errorOverlayMiddleware = require('react-dev-utils/errorOverlayMiddleware'); 5 | const evalSourceMapMiddleware = require('react-dev-utils/evalSourceMapMiddleware'); 6 | const noopServiceWorkerMiddleware = require('react-dev-utils/noopServiceWorkerMiddleware'); 7 | const ignoredFiles = require('react-dev-utils/ignoredFiles'); 8 | const redirectServedPath = require('react-dev-utils/redirectServedPathMiddleware'); 9 | const paths = require('./paths'); 10 | const getHttpsConfig = require('./getHttpsConfig'); 11 | 12 | const host = process.env.HOST || '0.0.0.0'; 13 | const sockHost = process.env.WDS_SOCKET_HOST; 14 | const sockPath = process.env.WDS_SOCKET_PATH; // default: '/sockjs-node' 15 | const sockPort = process.env.WDS_SOCKET_PORT; 16 | 17 | module.exports = function (proxy, allowedHost) { 18 | return { 19 | // WebpackDevServer 2.4.3 introduced a security fix that prevents remote 20 | // websites from potentially accessing local content through DNS rebinding: 21 | // https://github.com/webpack/webpack-dev-server/issues/887 22 | // https://medium.com/webpack/webpack-dev-server-middleware-security-issues-1489d950874a 23 | // However, it made several existing use cases such as development in cloud 24 | // environment or subdomains in development significantly more complicated: 25 | // https://github.com/facebook/create-react-app/issues/2271 26 | // https://github.com/facebook/create-react-app/issues/2233 27 | // While we're investigating better solutions, for now we will take a 28 | // compromise. Since our WDS configuration only serves files in the `public` 29 | // folder we won't consider accessing them a vulnerability. However, if you 30 | // use the `proxy` feature, it gets more dangerous because it can expose 31 | // remote code execution vulnerabilities in backends like Django and Rails. 32 | // So we will disable the host check normally, but enable it if you have 33 | // specified the `proxy` setting. Finally, we let you override it if you 34 | // really know what you're doing with a special environment variable. 35 | disableHostCheck: 36 | !proxy || process.env.DANGEROUSLY_DISABLE_HOST_CHECK === 'true', 37 | // Enable gzip compression of generated files. 38 | compress: true, 39 | // Silence WebpackDevServer's own logs since they're generally not useful. 40 | // It will still show compile warnings and errors with this setting. 41 | clientLogLevel: 'none', 42 | // By default WebpackDevServer serves physical files from current directory 43 | // in addition to all the virtual build products that it serves from memory. 44 | // This is confusing because those files won’t automatically be available in 45 | // production build folder unless we copy them. However, copying the whole 46 | // project directory is dangerous because we may expose sensitive files. 47 | // Instead, we establish a convention that only files in `public` directory 48 | // get served. Our build script will copy `public` into the `build` folder. 49 | // In `index.html`, you can get URL of `public` folder with %PUBLIC_URL%: 50 | // 51 | // In JavaScript code, you can access it with `process.env.PUBLIC_URL`. 52 | // Note that we only recommend to use `public` folder as an escape hatch 53 | // for files like `favicon.ico`, `manifest.json`, and libraries that are 54 | // for some reason broken when imported through webpack. If you just want to 55 | // use an image, put it in `src` and `import` it from JavaScript instead. 56 | contentBase: paths.appPublic, 57 | contentBasePublicPath: paths.publicUrlOrPath, 58 | // By default files from `contentBase` will not trigger a page reload. 59 | watchContentBase: true, 60 | // Enable hot reloading server. It will provide WDS_SOCKET_PATH endpoint 61 | // for the WebpackDevServer client so it can learn when the files were 62 | // updated. The WebpackDevServer client is included as an entry point 63 | // in the webpack development configuration. Note that only changes 64 | // to CSS are currently hot reloaded. JS changes will refresh the browser. 65 | hot: true, 66 | // Use 'ws' instead of 'sockjs-node' on server since we're using native 67 | // websockets in `webpackHotDevClient`. 68 | transportMode: 'ws', 69 | // Prevent a WS client from getting injected as we're already including 70 | // `webpackHotDevClient`. 71 | injectClient: false, 72 | // Enable custom sockjs pathname for websocket connection to hot reloading server. 73 | // Enable custom sockjs hostname, pathname and port for websocket connection 74 | // to hot reloading server. 75 | sockHost, 76 | sockPath, 77 | sockPort, 78 | // It is important to tell WebpackDevServer to use the same "publicPath" path as 79 | // we specified in the webpack config. When homepage is '.', default to serving 80 | // from the root. 81 | // remove last slash so user can land on `/test` instead of `/test/` 82 | publicPath: paths.publicUrlOrPath.slice(0, -1), 83 | // WebpackDevServer is noisy by default so we emit custom message instead 84 | // by listening to the compiler events with `compiler.hooks[...].tap` calls above. 85 | quiet: true, 86 | // Reportedly, this avoids CPU overload on some systems. 87 | // https://github.com/facebook/create-react-app/issues/293 88 | // src/node_modules is not ignored to support absolute imports 89 | // https://github.com/facebook/create-react-app/issues/1065 90 | watchOptions: { 91 | ignored: ignoredFiles(paths.appSrc), 92 | }, 93 | https: getHttpsConfig(), 94 | host, 95 | overlay: false, 96 | historyApiFallback: { 97 | // Paths with dots should still use the history fallback. 98 | // See https://github.com/facebook/create-react-app/issues/387. 99 | disableDotRule: true, 100 | index: paths.publicUrlOrPath, 101 | }, 102 | public: allowedHost, 103 | // `proxy` is run between `before` and `after` `webpack-dev-server` hooks 104 | proxy, 105 | before(app, server) { 106 | // Keep `evalSourceMapMiddleware` and `errorOverlayMiddleware` 107 | // middlewares before `redirectServedPath` otherwise will not have any effect 108 | // This lets us fetch source contents from webpack for the error overlay 109 | app.use(evalSourceMapMiddleware(server)); 110 | // This lets us open files from the runtime error overlay. 111 | app.use(errorOverlayMiddleware()); 112 | 113 | if (fs.existsSync(paths.proxySetup)) { 114 | // This registers user provided middleware for proxy reasons 115 | require(paths.proxySetup)(app); 116 | } 117 | }, 118 | after(app) { 119 | // Redirect to `PUBLIC_URL` or `homepage` from `package.json` if url not match 120 | app.use(redirectServedPath(paths.publicUrlOrPath)); 121 | 122 | // This service worker file is effectively a 'no-op' that will reset any 123 | // previous service worker registered for the same host:port combination. 124 | // We do this in development to avoid hitting the production cache if 125 | // it used the same host and port. 126 | // https://github.com/facebook/create-react-app/issues/2272#issuecomment-302832432 127 | app.use(noopServiceWorkerMiddleware(paths.publicUrlOrPath)); 128 | }, 129 | }; 130 | }; 131 | -------------------------------------------------------------------------------- /examples/react-autoi18n-cli/scripts/build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'production'; 5 | process.env.NODE_ENV = 'production'; 6 | 7 | // Makes the script crash on unhandled rejections instead of silently 8 | // ignoring them. In the future, promise rejections that are not handled will 9 | // terminate the Node.js process with a non-zero exit code. 10 | process.on('unhandledRejection', err => { 11 | throw err; 12 | }); 13 | 14 | // Ensure environment variables are read. 15 | require('../config/env'); 16 | 17 | 18 | const path = require('path'); 19 | const chalk = require('react-dev-utils/chalk'); 20 | const fs = require('fs-extra'); 21 | const bfj = require('bfj'); 22 | const webpack = require('webpack'); 23 | const configFactory = require('../config/webpack.config'); 24 | const paths = require('../config/paths'); 25 | const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles'); 26 | const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages'); 27 | const printHostingInstructions = require('react-dev-utils/printHostingInstructions'); 28 | const FileSizeReporter = require('react-dev-utils/FileSizeReporter'); 29 | const printBuildError = require('react-dev-utils/printBuildError'); 30 | 31 | const measureFileSizesBeforeBuild = 32 | FileSizeReporter.measureFileSizesBeforeBuild; 33 | const printFileSizesAfterBuild = FileSizeReporter.printFileSizesAfterBuild; 34 | const useYarn = fs.existsSync(paths.yarnLockFile); 35 | 36 | // These sizes are pretty large. We'll warn for bundles exceeding them. 37 | const WARN_AFTER_BUNDLE_GZIP_SIZE = 512 * 1024; 38 | const WARN_AFTER_CHUNK_GZIP_SIZE = 1024 * 1024; 39 | 40 | const isInteractive = process.stdout.isTTY; 41 | 42 | // Warn and crash if required files are missing 43 | if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) { 44 | process.exit(1); 45 | } 46 | 47 | const argv = process.argv.slice(2); 48 | const writeStatsJson = argv.indexOf('--stats') !== -1; 49 | 50 | // Generate configuration 51 | const config = configFactory('production'); 52 | 53 | // We require that you explicitly set browsers and do not fall back to 54 | // browserslist defaults. 55 | const { checkBrowsers } = require('react-dev-utils/browsersHelper'); 56 | checkBrowsers(paths.appPath, isInteractive) 57 | .then(() => { 58 | // First, read the current file sizes in build directory. 59 | // This lets us display how much they changed later. 60 | return measureFileSizesBeforeBuild(paths.appBuild); 61 | }) 62 | .then(previousFileSizes => { 63 | // Remove all content but keep the directory so that 64 | // if you're in it, you don't end up in Trash 65 | fs.emptyDirSync(paths.appBuild); 66 | // Merge with the public folder 67 | copyPublicFolder(); 68 | // Start the webpack build 69 | return build(previousFileSizes); 70 | }) 71 | .then( 72 | ({ stats, previousFileSizes, warnings }) => { 73 | if (warnings.length) { 74 | console.log(chalk.yellow('Compiled with warnings.\n')); 75 | console.log(warnings.join('\n\n')); 76 | console.log( 77 | '\nSearch for the ' + 78 | chalk.underline(chalk.yellow('keywords')) + 79 | ' to learn more about each warning.' 80 | ); 81 | console.log( 82 | 'To ignore, add ' + 83 | chalk.cyan('// eslint-disable-next-line') + 84 | ' to the line before.\n' 85 | ); 86 | } else { 87 | console.log(chalk.green('Compiled successfully.\n')); 88 | } 89 | 90 | console.log('File sizes after gzip:\n'); 91 | printFileSizesAfterBuild( 92 | stats, 93 | previousFileSizes, 94 | paths.appBuild, 95 | WARN_AFTER_BUNDLE_GZIP_SIZE, 96 | WARN_AFTER_CHUNK_GZIP_SIZE 97 | ); 98 | console.log(); 99 | 100 | const appPackage = require(paths.appPackageJson); 101 | const publicUrl = paths.publicUrlOrPath; 102 | const publicPath = config.output.publicPath; 103 | const buildFolder = path.relative(process.cwd(), paths.appBuild); 104 | printHostingInstructions( 105 | appPackage, 106 | publicUrl, 107 | publicPath, 108 | buildFolder, 109 | useYarn 110 | ); 111 | }, 112 | err => { 113 | const tscCompileOnError = process.env.TSC_COMPILE_ON_ERROR === 'true'; 114 | if (tscCompileOnError) { 115 | console.log( 116 | chalk.yellow( 117 | 'Compiled with the following type errors (you may want to check these before deploying your app):\n' 118 | ) 119 | ); 120 | printBuildError(err); 121 | } else { 122 | console.log(chalk.red('Failed to compile.\n')); 123 | printBuildError(err); 124 | process.exit(1); 125 | } 126 | } 127 | ) 128 | .catch(err => { 129 | if (err && err.message) { 130 | console.log(err.message); 131 | } 132 | process.exit(1); 133 | }); 134 | 135 | // Create the production build and print the deployment instructions. 136 | function build(previousFileSizes) { 137 | console.log('Creating an optimized production build...'); 138 | 139 | const compiler = webpack(config); 140 | return new Promise((resolve, reject) => { 141 | compiler.run((err, stats) => { 142 | let messages; 143 | if (err) { 144 | if (!err.message) { 145 | return reject(err); 146 | } 147 | 148 | let errMessage = err.message; 149 | 150 | // Add additional information for postcss errors 151 | if (Object.prototype.hasOwnProperty.call(err, 'postcssNode')) { 152 | errMessage += 153 | '\nCompileError: Begins at CSS selector ' + 154 | err['postcssNode'].selector; 155 | } 156 | 157 | messages = formatWebpackMessages({ 158 | errors: [errMessage], 159 | warnings: [], 160 | }); 161 | } else { 162 | messages = formatWebpackMessages( 163 | stats.toJson({ all: false, warnings: true, errors: true }) 164 | ); 165 | } 166 | if (messages.errors.length) { 167 | // Only keep the first error. Others are often indicative 168 | // of the same problem, but confuse the reader with noise. 169 | if (messages.errors.length > 1) { 170 | messages.errors.length = 1; 171 | } 172 | return reject(new Error(messages.errors.join('\n\n'))); 173 | } 174 | if ( 175 | process.env.CI && 176 | (typeof process.env.CI !== 'string' || 177 | process.env.CI.toLowerCase() !== 'false') && 178 | messages.warnings.length 179 | ) { 180 | console.log( 181 | chalk.yellow( 182 | '\nTreating warnings as errors because process.env.CI = true.\n' + 183 | 'Most CI servers set it automatically.\n' 184 | ) 185 | ); 186 | return reject(new Error(messages.warnings.join('\n\n'))); 187 | } 188 | 189 | const resolveArgs = { 190 | stats, 191 | previousFileSizes, 192 | warnings: messages.warnings, 193 | }; 194 | 195 | if (writeStatsJson) { 196 | return bfj 197 | .write(paths.appBuild + '/bundle-stats.json', stats.toJson()) 198 | .then(() => resolve(resolveArgs)) 199 | .catch(error => reject(new Error(error))); 200 | } 201 | 202 | return resolve(resolveArgs); 203 | }); 204 | }); 205 | } 206 | 207 | function copyPublicFolder() { 208 | fs.copySync(paths.appPublic, paths.appBuild, { 209 | dereference: true, 210 | filter: file => file !== paths.appHtml, 211 | }); 212 | } 213 | -------------------------------------------------------------------------------- /examples/react-autoi18n-loaders/scripts/build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'production'; 5 | process.env.NODE_ENV = 'production'; 6 | 7 | // Makes the script crash on unhandled rejections instead of silently 8 | // ignoring them. In the future, promise rejections that are not handled will 9 | // terminate the Node.js process with a non-zero exit code. 10 | process.on('unhandledRejection', err => { 11 | throw err; 12 | }); 13 | 14 | // Ensure environment variables are read. 15 | require('../config/env'); 16 | 17 | 18 | const path = require('path'); 19 | const chalk = require('react-dev-utils/chalk'); 20 | const fs = require('fs-extra'); 21 | const bfj = require('bfj'); 22 | const webpack = require('webpack'); 23 | const configFactory = require('../config/webpack.config'); 24 | const paths = require('../config/paths'); 25 | const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles'); 26 | const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages'); 27 | const printHostingInstructions = require('react-dev-utils/printHostingInstructions'); 28 | const FileSizeReporter = require('react-dev-utils/FileSizeReporter'); 29 | const printBuildError = require('react-dev-utils/printBuildError'); 30 | 31 | const measureFileSizesBeforeBuild = 32 | FileSizeReporter.measureFileSizesBeforeBuild; 33 | const printFileSizesAfterBuild = FileSizeReporter.printFileSizesAfterBuild; 34 | const useYarn = fs.existsSync(paths.yarnLockFile); 35 | 36 | // These sizes are pretty large. We'll warn for bundles exceeding them. 37 | const WARN_AFTER_BUNDLE_GZIP_SIZE = 512 * 1024; 38 | const WARN_AFTER_CHUNK_GZIP_SIZE = 1024 * 1024; 39 | 40 | const isInteractive = process.stdout.isTTY; 41 | 42 | // Warn and crash if required files are missing 43 | if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) { 44 | process.exit(1); 45 | } 46 | 47 | const argv = process.argv.slice(2); 48 | const writeStatsJson = argv.indexOf('--stats') !== -1; 49 | 50 | // Generate configuration 51 | const config = configFactory('production'); 52 | 53 | // We require that you explicitly set browsers and do not fall back to 54 | // browserslist defaults. 55 | const { checkBrowsers } = require('react-dev-utils/browsersHelper'); 56 | checkBrowsers(paths.appPath, isInteractive) 57 | .then(() => { 58 | // First, read the current file sizes in build directory. 59 | // This lets us display how much they changed later. 60 | return measureFileSizesBeforeBuild(paths.appBuild); 61 | }) 62 | .then(previousFileSizes => { 63 | // Remove all content but keep the directory so that 64 | // if you're in it, you don't end up in Trash 65 | fs.emptyDirSync(paths.appBuild); 66 | // Merge with the public folder 67 | copyPublicFolder(); 68 | // Start the webpack build 69 | return build(previousFileSizes); 70 | }) 71 | .then( 72 | ({ stats, previousFileSizes, warnings }) => { 73 | if (warnings.length) { 74 | console.log(chalk.yellow('Compiled with warnings.\n')); 75 | console.log(warnings.join('\n\n')); 76 | console.log( 77 | '\nSearch for the ' + 78 | chalk.underline(chalk.yellow('keywords')) + 79 | ' to learn more about each warning.' 80 | ); 81 | console.log( 82 | 'To ignore, add ' + 83 | chalk.cyan('// eslint-disable-next-line') + 84 | ' to the line before.\n' 85 | ); 86 | } else { 87 | console.log(chalk.green('Compiled successfully.\n')); 88 | } 89 | 90 | console.log('File sizes after gzip:\n'); 91 | printFileSizesAfterBuild( 92 | stats, 93 | previousFileSizes, 94 | paths.appBuild, 95 | WARN_AFTER_BUNDLE_GZIP_SIZE, 96 | WARN_AFTER_CHUNK_GZIP_SIZE 97 | ); 98 | console.log(); 99 | 100 | const appPackage = require(paths.appPackageJson); 101 | const publicUrl = paths.publicUrlOrPath; 102 | const publicPath = config.output.publicPath; 103 | const buildFolder = path.relative(process.cwd(), paths.appBuild); 104 | printHostingInstructions( 105 | appPackage, 106 | publicUrl, 107 | publicPath, 108 | buildFolder, 109 | useYarn 110 | ); 111 | }, 112 | err => { 113 | const tscCompileOnError = process.env.TSC_COMPILE_ON_ERROR === 'true'; 114 | if (tscCompileOnError) { 115 | console.log( 116 | chalk.yellow( 117 | 'Compiled with the following type errors (you may want to check these before deploying your app):\n' 118 | ) 119 | ); 120 | printBuildError(err); 121 | } else { 122 | console.log(chalk.red('Failed to compile.\n')); 123 | printBuildError(err); 124 | process.exit(1); 125 | } 126 | } 127 | ) 128 | .catch(err => { 129 | if (err && err.message) { 130 | console.log(err.message); 131 | } 132 | process.exit(1); 133 | }); 134 | 135 | // Create the production build and print the deployment instructions. 136 | function build(previousFileSizes) { 137 | console.log('Creating an optimized production build...'); 138 | 139 | const compiler = webpack(config); 140 | return new Promise((resolve, reject) => { 141 | compiler.run((err, stats) => { 142 | let messages; 143 | if (err) { 144 | if (!err.message) { 145 | return reject(err); 146 | } 147 | 148 | let errMessage = err.message; 149 | 150 | // Add additional information for postcss errors 151 | if (Object.prototype.hasOwnProperty.call(err, 'postcssNode')) { 152 | errMessage += 153 | '\nCompileError: Begins at CSS selector ' + 154 | err['postcssNode'].selector; 155 | } 156 | 157 | messages = formatWebpackMessages({ 158 | errors: [errMessage], 159 | warnings: [], 160 | }); 161 | } else { 162 | messages = formatWebpackMessages( 163 | stats.toJson({ all: false, warnings: true, errors: true }) 164 | ); 165 | } 166 | if (messages.errors.length) { 167 | // Only keep the first error. Others are often indicative 168 | // of the same problem, but confuse the reader with noise. 169 | if (messages.errors.length > 1) { 170 | messages.errors.length = 1; 171 | } 172 | return reject(new Error(messages.errors.join('\n\n'))); 173 | } 174 | if ( 175 | process.env.CI && 176 | (typeof process.env.CI !== 'string' || 177 | process.env.CI.toLowerCase() !== 'false') && 178 | messages.warnings.length 179 | ) { 180 | console.log( 181 | chalk.yellow( 182 | '\nTreating warnings as errors because process.env.CI = true.\n' + 183 | 'Most CI servers set it automatically.\n' 184 | ) 185 | ); 186 | return reject(new Error(messages.warnings.join('\n\n'))); 187 | } 188 | 189 | const resolveArgs = { 190 | stats, 191 | previousFileSizes, 192 | warnings: messages.warnings, 193 | }; 194 | 195 | if (writeStatsJson) { 196 | return bfj 197 | .write(paths.appBuild + '/bundle-stats.json', stats.toJson()) 198 | .then(() => resolve(resolveArgs)) 199 | .catch(error => reject(new Error(error))); 200 | } 201 | 202 | return resolve(resolveArgs); 203 | }); 204 | }); 205 | } 206 | 207 | function copyPublicFolder() { 208 | fs.copySync(paths.appPublic, paths.appBuild, { 209 | dereference: true, 210 | filter: file => file !== paths.appHtml, 211 | }); 212 | } 213 | --------------------------------------------------------------------------------