Skip to content

Commit 77390b5

Browse files
authored
feat: intlify vue plugin for v1 (#173)
1 parent 5fa6e39 commit 77390b5

12 files changed

+3182
-2735
lines changed

.eslintrc.js

+5-3
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ module.exports = {
1616
'plugin:vue-libs/recommended',
1717
'plugin:@typescript-eslint/recommended',
1818
'plugin:@typescript-eslint/eslint-recommended',
19-
'prettier/@typescript-eslint',
20-
'plugin:prettier/recommended'
19+
'plugin:prettier/recommended',
20+
'prettier'
2121
],
2222
plugins: ['@typescript-eslint'],
2323
parser: 'vue-eslint-parser',
@@ -29,6 +29,8 @@ module.exports = {
2929
'object-curly-spacing': 'off',
3030
'@typescript-eslint/explicit-function-return-type': 'off',
3131
'@typescript-eslint/member-delimiter-style': 'off',
32-
'@typescript-eslint/no-use-before-define': 'off'
32+
'@typescript-eslint/no-use-before-define': 'off',
33+
'@typescript-eslint/no-non-null-assertion': 'off',
34+
'@typescript-eslint/ban-ts-comment': 'off'
3335
}
3436
}

package.json

+25-24
Original file line numberDiff line numberDiff line change
@@ -24,46 +24,46 @@
2424
}
2525
},
2626
"dependencies": {
27+
"@intlify/shared": "^9.0.0",
2728
"js-yaml": "^3.13.1",
2829
"json5": "^2.1.1"
2930
},
3031
"devDependencies": {
31-
"@types/jest": "^25.0.0",
32+
"@types/jest": "^26.0.16",
3233
"@types/js-yaml": "^3.12.1",
33-
"@types/jsdom": "^16.0.0",
34+
"@types/jsdom": "^16.2.5",
3435
"@types/json5": "^0.0.30",
3536
"@types/memory-fs": "^0.3.2",
36-
"@types/node": "^13.1.4",
37-
"@types/webpack": "^4.41.1",
37+
"@types/node": "^14.14.10",
38+
"@types/webpack": "^4.41.26",
3839
"@types/webpack-merge": "^4.1.5",
39-
"@typescript-eslint/eslint-plugin": "^2.26.0",
40-
"@typescript-eslint/parser": "^2.26.0",
41-
"@typescript-eslint/typescript-estree": "^2.26.0",
40+
"@typescript-eslint/eslint-plugin": "^4.15.0",
41+
"@typescript-eslint/parser": "^4.15.0",
4242
"babel-loader": "^8.1.0",
43-
"eslint": "^6.8.0",
44-
"eslint-config-prettier": "^6.10.1",
45-
"eslint-plugin-prettier": "^3.1.2",
43+
"eslint": "^7.21.0",
44+
"eslint-config-prettier": "^8.1.0",
45+
"eslint-plugin-prettier": "^3.3.1",
4646
"eslint-plugin-vue-libs": "^4.0.0",
47-
"jest": "^25.2.4",
47+
"jest": "^26.6.3",
4848
"jest-puppeteer": "^4.4.0",
49-
"jest-watch-typeahead": "^0.5.0",
50-
"jsdom": "^16.0.0",
51-
"lerna-changelog": "^1.0.0",
49+
"jest-watch-typeahead": "^0.6.0",
50+
"jsdom": "^16.4.0",
51+
"lerna-changelog": "^1.0.1",
5252
"memory-fs": "^0.5.0",
53-
"opener": "^1.5.1",
53+
"opener": "^1.5.2",
5454
"prettier": "^2.0.4",
5555
"puppeteer": "^2.1.1",
56-
"shipjs": "^0.18.0",
57-
"ts-jest": "^25.3.0",
58-
"typescript": "^3.8.3",
59-
"typescript-eslint-language-service": "^2.0.3",
56+
"shipjs": "^0.23.0",
57+
"ts-jest": "^26.4.4",
58+
"typescript": "^4.1.3",
59+
"typescript-eslint-language-service": "^4.1.3",
6060
"vue": "^2.6.11",
61-
"vue-i18n": "^8.16.0",
62-
"vue-loader": "^15.7.0",
61+
"vue-i18n": "^8.23.0",
62+
"vue-loader": "^15.9.6",
6363
"vue-template-compiler": "^2.6.11",
64-
"webpack": "^4.42.1",
65-
"webpack-cli": "^3.3.11",
66-
"webpack-dev-server": "^3.10.3",
64+
"webpack": "^4.46.0",
65+
"webpack-cli": "^3.3.12",
66+
"webpack-dev-server": "^3.11.0",
6767
"webpack-merge": "^4.2.2"
6868
},
6969
"engines": {
@@ -95,6 +95,7 @@
9595
"lint:fix": "yarn lint --fix",
9696
"release:prepare": "shipjs prepare",
9797
"release:trigger": "shipjs trigger",
98+
"fix": "yarn format:fix && yarn lint:fix",
9899
"format": "prettier --config .prettierrc --ignore-path .prettierignore '**/*.{js,json,html}'",
99100
"format:fix": "yarn format --write",
100101
"test": "yarn lint && yarn test:cover && yarn test:e2e",

ship.config.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ async function commitChangelog(current, next) {
3636
}
3737

3838
module.exports = {
39-
mergeStrategy: { toSameBranch: ['master'] },
39+
mergeStrategy: { toSameBranch: ['v1.x'] },
4040
monorepo: undefined,
4141
updateChangelog: false,
4242
beforeCommitChanges: ({ nextVersion, exec, dir }) => {

src/plugin.ts

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
;(async () => {
2+
try {
3+
await import('vue-i18n')
4+
} catch (e) {
5+
throw new Error(
6+
'@intlify/vue-i18n-loader requires vue-i18n to be present in the dependency tree.'
7+
)
8+
}
9+
})()
10+
11+
import webpack from 'webpack'
12+
13+
declare class IntlifyVuePlugin {
14+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
15+
constructor(optoins: Record<string, any>)
16+
apply(compiler: webpack.Compiler): void
17+
}
18+
19+
let Plugin: typeof IntlifyVuePlugin
20+
21+
console.warn(
22+
`[@intlify/vue-i18n-loader] IntlifyVuePlugin is experimental! This plugin is used for Intlify tools. Don't use this plugin to enhancement Component options of your application.`
23+
)
24+
// console.log('[@intlify/vue-i18n-loader] webpack version:', webpack.version)
25+
26+
if (webpack.version && webpack.version[0] > '4') {
27+
// webpack5 and upper
28+
Plugin = require('./pluginWebpack5').default // eslint-disable-line @typescript-eslint/no-var-requires
29+
} else {
30+
// webpack4 and lower
31+
Plugin = require('./pluginWebpack4').default // eslint-disable-line @typescript-eslint/no-var-requires
32+
}
33+
34+
export default Plugin

src/pluginWebpack4.ts

+238
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
/* eslint-disable @typescript-eslint/no-explicit-any */
2+
3+
import { parse as parseQuery } from 'querystring'
4+
import webpack from 'webpack'
5+
import { ReplaceSource } from 'webpack-sources'
6+
import { isFunction, isObject, isRegExp, isString } from '@intlify/shared'
7+
const Dependency = require('webpack/lib/Dependency') // eslint-disable-line @typescript-eslint/no-var-requires
8+
const NullFactory = require('webpack/lib/NullFactory') // eslint-disable-line @typescript-eslint/no-var-requires
9+
10+
const PLUGIN_ID = 'IntlifyVuePlugin'
11+
12+
interface NormalModule extends webpack.compilation.Module {
13+
resource?: string
14+
request?: string
15+
userRequest?: string
16+
addDependency(dep: unknown): void
17+
parser?: webpack.compilation.normalModuleFactory.Parser
18+
loaders?: Array<{
19+
loader: string
20+
options: any
21+
indent?: string
22+
type?: string
23+
}>
24+
}
25+
26+
type InjectionValues = Record<string, any>
27+
28+
class VueComponentDependency extends Dependency {
29+
static Template: VueComponentDependencyTemplate
30+
script?: NormalModule
31+
template?: NormalModule
32+
values: InjectionValues
33+
statement: any
34+
35+
constructor(
36+
script: NormalModule | undefined,
37+
template: NormalModule | undefined,
38+
values: InjectionValues,
39+
statement: any
40+
) {
41+
super()
42+
this.script = script
43+
this.template = template
44+
this.values = values
45+
this.statement = statement
46+
}
47+
48+
get type() {
49+
return 'harmony export expression'
50+
}
51+
52+
getExports() {
53+
return {
54+
exports: ['default'],
55+
dependencies: undefined
56+
}
57+
}
58+
59+
updateHash(hash: any) {
60+
super.updateHash(hash)
61+
const scriptModule = this.script
62+
hash.update(
63+
(scriptModule &&
64+
(!scriptModule.buildMeta || scriptModule.buildMeta.exportsType)) + ''
65+
)
66+
hash.update((scriptModule && scriptModule.id) + '')
67+
const templateModule = this.template
68+
hash.update(
69+
(templateModule &&
70+
(!templateModule.buildMeta || templateModule.buildMeta.exportsType)) +
71+
''
72+
)
73+
hash.update((templateModule && templateModule.id) + '')
74+
}
75+
}
76+
77+
function stringifyObj(obj: Record<string, any>): string {
78+
return `Object({${Object.keys(obj)
79+
.map(key => {
80+
const code = obj[key]
81+
return `${JSON.stringify(key)}:${toCode(code)}`
82+
})
83+
.join(',')}})`
84+
}
85+
86+
function toCode(code: any): string {
87+
if (code === null) {
88+
return 'null'
89+
}
90+
91+
if (code === undefined) {
92+
return 'undefined'
93+
}
94+
95+
if (isString(code)) {
96+
return JSON.stringify(code)
97+
}
98+
99+
if (isRegExp(code) && code.toString) {
100+
return code.toString()
101+
}
102+
103+
if (isFunction(code) && code.toString) {
104+
return '(' + code.toString() + ')'
105+
}
106+
107+
if (isObject(code)) {
108+
return stringifyObj(code)
109+
}
110+
111+
return code + ''
112+
}
113+
114+
function generateCode(dep: VueComponentDependency, importVar: string): string {
115+
const injectionCodes = ['']
116+
Object.keys(dep.values).forEach(key => {
117+
const code = dep.values[key]
118+
if (isFunction(code)) {
119+
injectionCodes.push(`${importVar}.${key} = ${JSON.stringify(code(dep))}`)
120+
} else {
121+
injectionCodes.push(`${importVar}.${key} = ${toCode(code)}`)
122+
}
123+
})
124+
125+
let ret = injectionCodes.join('\n')
126+
ret = ret.length > 0 ? `\n${ret}\n` : ''
127+
return (ret += `/* harmony default export */ __webpack_exports__["default"] = (${importVar});`)
128+
}
129+
130+
class VueComponentDependencyTemplate {
131+
apply(dep: VueComponentDependency, source: ReplaceSource) {
132+
const repleacements = source.replacements
133+
const orgReplace = repleacements[repleacements.length - 1]
134+
const code = generateCode(dep, 'component.exports')
135+
// console.log('generateCode', code, dep.statement, orgReplace)
136+
source.replace(orgReplace.start, orgReplace.end, code)
137+
}
138+
}
139+
140+
VueComponentDependency.Template = VueComponentDependencyTemplate
141+
142+
function getScriptBlockModule(parser: any): NormalModule | undefined {
143+
return parser.state.current.dependencies.find((dep: any) => {
144+
const req = dep.userRequest || dep.request
145+
if (req && dep.originModule) {
146+
const query = parseQuery(req)
147+
return query.type === 'script' && query.lang === 'js'
148+
} else {
149+
return false
150+
}
151+
})
152+
}
153+
154+
function getTemplateBlockModule(parser: any): NormalModule | undefined {
155+
return parser.state.current.dependencies.find((dep: any) => {
156+
const req = dep.userRequest || dep.request
157+
if (req && dep.originModule) {
158+
const query = parseQuery(req)
159+
return query.type === 'template'
160+
} else {
161+
return false
162+
}
163+
})
164+
}
165+
166+
function toVueComponentDependency(parser: any, values: InjectionValues) {
167+
return function vueComponentDependencyw(statement: any) {
168+
// console.log('toVueComponentDependency##statement', statement)
169+
const dep = new VueComponentDependency(
170+
getScriptBlockModule(parser),
171+
getTemplateBlockModule(parser),
172+
values,
173+
statement
174+
)
175+
// dep.loc = statement.loc
176+
parser.state.current.addDependency(dep)
177+
return true
178+
}
179+
}
180+
181+
export default class IntlifyVuePlugin implements webpack.Plugin {
182+
injections: InjectionValues
183+
184+
constructor(injections: InjectionValues = {}) {
185+
this.injections = injections
186+
}
187+
188+
apply(compiler: webpack.Compiler): void {
189+
const injections = this.injections
190+
191+
compiler.hooks.compilation.tap(
192+
PLUGIN_ID,
193+
(compilation, { normalModuleFactory }) => {
194+
compilation.dependencyFactories.set(
195+
// @ts-ignore
196+
VueComponentDependency,
197+
new NullFactory()
198+
)
199+
compilation.dependencyTemplates.set(
200+
// @ts-ignore
201+
VueComponentDependency,
202+
// @ts-ignore
203+
new VueComponentDependency.Template()
204+
)
205+
206+
const handler = (
207+
parser: webpack.compilation.normalModuleFactory.Parser
208+
) => {
209+
parser.hooks.exportExpression.tap(
210+
PLUGIN_ID,
211+
(statement, declaration) => {
212+
if (
213+
(parser as any).state.module.resource.endsWith('.vue') &&
214+
declaration.object.name === 'component' &&
215+
declaration.property.name === 'exports'
216+
) {
217+
// console.log('exportExpression', statement, declaration)
218+
return toVueComponentDependency(parser, injections)(statement)
219+
}
220+
}
221+
)
222+
}
223+
224+
normalModuleFactory.hooks.parser
225+
.for('javascript/auto')
226+
.tap(PLUGIN_ID, handler)
227+
normalModuleFactory.hooks.parser
228+
.for('javascript/dynamic')
229+
.tap(PLUGIN_ID, handler)
230+
normalModuleFactory.hooks.parser
231+
.for('javascript/esm')
232+
.tap(PLUGIN_ID, handler)
233+
}
234+
)
235+
}
236+
}
237+
238+
/* eslint-enable @typescript-eslint/no-explicit-any */

0 commit comments

Comments
 (0)