Skip to content

Commit 6a9c21b

Browse files
authored
feat: intlify vue plugin for webpack5 (#171)
1 parent fa3ff35 commit 6a9c21b

File tree

3 files changed

+246
-4
lines changed

3 files changed

+246
-4
lines changed

src/plugin.ts

+12-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
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+
111
import webpack from 'webpack'
212

3-
declare class IntlifyVuePlugin implements webpack.Plugin {
13+
declare class IntlifyVuePlugin {
414
// eslint-disable-next-line @typescript-eslint/no-explicit-any
515
constructor(optoins: Record<string, any>)
616
apply(compiler: webpack.Compiler): void
@@ -11,6 +21,7 @@ let Plugin: typeof IntlifyVuePlugin
1121
console.warn(
1222
`[@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.`
1323
)
24+
// console.log('[@intlify/vue-i18n-loader] webpack version:', webpack.version)
1425

1526
if (webpack.version && webpack.version[0] > '4') {
1627
// webpack5 and upper

src/pluginWebpack4.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ type InjectionValues = Record<string, any>
2828
class VueComponentDependency extends Dependency {
2929
static Template: VueComponentDependencyTemplate
3030
script?: NormalModule
31-
templa?: NormalModule
31+
template?: NormalModule
3232
values: InjectionValues
3333
statement: any
3434

src/pluginWebpack5.ts

+233-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,239 @@
1+
/* eslint-disable @typescript-eslint/no-explicit-any */
2+
3+
import { parse as parseQuery } from 'querystring'
14
import webpack from 'webpack'
5+
import { isFunction, isObject, isRegExp, isString } from '@intlify/shared'
6+
const Dependency = require('webpack/lib/Dependency') // eslint-disable-line @typescript-eslint/no-var-requires
7+
const NullFactory = require('webpack/lib/NullFactory') // eslint-disable-line @typescript-eslint/no-var-requires
8+
9+
const PLUGIN_ID = 'IntlifyVuePlugin'
10+
11+
type InjectionValues = Record<string, any>
12+
13+
class VueComponentDependency extends Dependency {
14+
static Template: VueComponentDependencyTemplate
15+
script?: any /* webpack.Dependency */
16+
template?: any /* webpack.Dependency */
17+
values: InjectionValues
18+
statement: any
19+
20+
constructor(
21+
script: any /* webpack.Dependency | undefined, */,
22+
template: any /* webpack.Dependency | undefined, */,
23+
values: InjectionValues,
24+
statement: any
25+
) {
26+
super()
27+
this.script = script
28+
this.template = template
29+
this.values = values
30+
this.statement = statement
31+
}
32+
33+
// @ts-ignore
34+
get type(): string {
35+
return 'harmony export expression'
36+
}
37+
38+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
39+
getExports(moduleGraph: any /* webpack.ModuleGraph */) {
40+
return {
41+
exports: ['default'],
42+
dependencies: undefined
43+
}
44+
}
245

3-
export default class IntlifyVuePlugin implements webpack.Plugin {
446
// eslint-disable-next-line @typescript-eslint/no-unused-vars
47+
updateHash(hash: any, context: any): void {
48+
super.updateHash(hash, context)
49+
const scriptModule: any = this.script
50+
hash.update(
51+
(scriptModule &&
52+
(!scriptModule.buildMeta || scriptModule.buildMeta.exportsType)) + ''
53+
)
54+
hash.update((scriptModule && scriptModule.id) + '')
55+
const templateModule: any = this.template
56+
hash.update(
57+
(templateModule &&
58+
(!templateModule.buildMeta || templateModule.buildMeta.exportsType)) +
59+
''
60+
)
61+
hash.update((templateModule && templateModule.id) + '')
62+
}
63+
}
64+
65+
function stringifyObj(obj: Record<string, any>): string {
66+
return `Object({${Object.keys(obj)
67+
.map(key => {
68+
const code = obj[key]
69+
return `${JSON.stringify(key)}:${toCode(code)}`
70+
})
71+
.join(',')}})`
72+
}
73+
74+
function toCode(code: any): string {
75+
if (code === null) {
76+
return 'null'
77+
}
78+
79+
if (code === undefined) {
80+
return 'undefined'
81+
}
82+
83+
if (isString(code)) {
84+
return JSON.stringify(code)
85+
}
86+
87+
if (isRegExp(code) && code.toString) {
88+
return code.toString()
89+
}
90+
91+
if (isFunction(code) && code.toString) {
92+
return '(' + code.toString() + ')'
93+
}
94+
95+
if (isObject(code)) {
96+
return stringifyObj(code)
97+
}
98+
99+
return code + ''
100+
}
101+
102+
function generateCode(dep: VueComponentDependency, importVar: string): string {
103+
const injectionCodes = ['']
104+
Object.keys(dep.values).forEach(key => {
105+
const code = dep.values[key]
106+
if (isFunction(code)) {
107+
injectionCodes.push(`${importVar}.${key} = ${JSON.stringify(code(dep))}`)
108+
} else {
109+
injectionCodes.push(`${importVar}.${key} = ${toCode(code)}`)
110+
}
111+
})
112+
113+
let ret = injectionCodes.join('\n')
114+
ret = ret.length > 0 ? `\n${ret}\n` : ''
115+
return (ret += `/* harmony default export */ __webpack_exports__["default"] = (${importVar});`)
116+
}
117+
118+
class VueComponentDependencyTemplate {
119+
apply(
120+
dep: VueComponentDependency,
121+
source: any /* webpack.sources.ReplaceSource */
122+
) {
123+
const repleacements = source.getReplacements()
124+
const orgReplace = repleacements[repleacements.length - 1]
125+
if (dep.statement.declaration.start !== orgReplace.start) {
126+
return
127+
}
128+
129+
const code = generateCode(dep, orgReplace.content)
130+
// console.log('generateCode', code, dep.statement, orgReplace)
131+
source.replace(orgReplace.start, orgReplace.end, code)
132+
}
133+
}
134+
135+
VueComponentDependency.Template = VueComponentDependencyTemplate
136+
137+
function getScriptBlockModule(
138+
parser: any /* webpack.javascript.JavascriptParser */
139+
) /* : webpack.Dependency | undefined */ {
140+
return parser.state.current.dependencies.find((dep: any) => {
141+
const req = dep.userRequest || dep.request
142+
if (req && dep.originModule) {
143+
const query = parseQuery(req)
144+
return query.type === 'script' && query.lang === 'js'
145+
} else {
146+
return false
147+
}
148+
})
149+
}
150+
151+
function getTemplateBlockModule(
152+
parser: any /* webpack.javascript.JavascriptParser */
153+
) /* : webpack.Dependency | undefined */ {
154+
return parser.state.current.dependencies.find((dep: any) => {
155+
const req = dep.userRequest || dep.request
156+
if (req && dep.originModule) {
157+
const query = parseQuery(req)
158+
return query.type === 'template'
159+
} else {
160+
return false
161+
}
162+
})
163+
}
164+
165+
function toVueComponentDependency(
166+
parser: any /* webpack.javascript.JavascriptParser */,
167+
values: InjectionValues
168+
) {
169+
return function vueComponentDependencyw(statement: any) {
170+
// console.log('toVueComponentDependency##statement', statement)
171+
const dep = new VueComponentDependency(
172+
getScriptBlockModule(parser),
173+
getTemplateBlockModule(parser),
174+
values,
175+
statement
176+
)
177+
// dep.loc = statement.loc
178+
parser.state.current.addDependency(dep)
179+
return true
180+
}
181+
}
182+
183+
export default class IntlifyVuePlugin {
184+
injections: InjectionValues
185+
186+
constructor(injections: InjectionValues = {}) {
187+
this.injections = injections
188+
}
189+
5190
apply(compiler: webpack.Compiler): void {
6-
console.error('[@intlify/vue-i18n-loader] Cannot support webpack5 yet')
191+
const injections = this.injections
192+
193+
compiler.hooks.compilation.tap(
194+
PLUGIN_ID,
195+
(compilation, { normalModuleFactory }) => {
196+
compilation.dependencyFactories.set(
197+
// @ts-ignore
198+
VueComponentDependency,
199+
new NullFactory()
200+
)
201+
compilation.dependencyTemplates.set(
202+
// @ts-ignore
203+
VueComponentDependency,
204+
// @ts-ignore
205+
new VueComponentDependency.Template()
206+
)
207+
208+
const handler = (
209+
parser: any /* webpack.javascript.JavascriptParser */
210+
) => {
211+
parser.hooks.exportExpression.tap(
212+
PLUGIN_ID,
213+
(statement: any, declaration: any) => {
214+
if (
215+
parser.state.module.resource.endsWith('.vue') &&
216+
declaration.name === 'script'
217+
) {
218+
// console.log('exportExpression', statement, declaration)
219+
return toVueComponentDependency(parser, injections)(statement)
220+
}
221+
}
222+
)
223+
}
224+
225+
normalModuleFactory.hooks.parser
226+
.for('javascript/auto')
227+
.tap(PLUGIN_ID, handler)
228+
normalModuleFactory.hooks.parser
229+
.for('javascript/dynamic')
230+
.tap(PLUGIN_ID, handler)
231+
normalModuleFactory.hooks.parser
232+
.for('javascript/esm')
233+
.tap(PLUGIN_ID, handler)
234+
}
235+
)
7236
}
8237
}
238+
239+
/* eslint-enable @typescript-eslint/no-explicit-any */

0 commit comments

Comments
 (0)