Skip to content

Commit c2c2092

Browse files
committed
fix(compiler): enhance npm module resolution with new script module handling and dependency ID resolution (#148)
1 parent 52c398d commit c2c2092

4 files changed

Lines changed: 312 additions & 77 deletions

File tree

fe/packages/compiler/__tests__/npm-resolver.spec.js

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,44 @@ describe('NpmResolver', () => {
113113
})
114114
})
115115

116+
describe('resolveScriptModule', () => {
117+
it('应该优先解析最近目录下的 npm 脚本模块', () => {
118+
const localPackagePath = path.join(tempDir, 'pages/feature/miniprogram_npm/westore')
119+
const rootPackagePath = path.join(tempDir, 'miniprogram_npm/westore')
120+
const pageFilePath = path.join(tempDir, 'pages/feature/index.js')
121+
122+
fs.mkdirSync(localPackagePath, { recursive: true })
123+
fs.mkdirSync(rootPackagePath, { recursive: true })
124+
fs.writeFileSync(path.join(localPackagePath, 'package.json'), JSON.stringify({ main: 'local.js' }))
125+
fs.writeFileSync(path.join(localPackagePath, 'local.js'), 'module.exports = "local"')
126+
fs.writeFileSync(path.join(rootPackagePath, 'package.json'), JSON.stringify({ main: 'root.js' }))
127+
fs.writeFileSync(path.join(rootPackagePath, 'root.js'), 'module.exports = "root"')
128+
129+
const result = npmResolver.resolveScriptModule('westore', pageFilePath, moduleId => {
130+
for (const ext of ['.js', '.ts']) {
131+
if (fs.existsSync(path.join(tempDir, `${moduleId}${ext}`))) {
132+
return moduleId
133+
}
134+
}
135+
136+
const packageJsonPath = path.join(tempDir, moduleId, 'package.json')
137+
if (fs.existsSync(packageJsonPath)) {
138+
const packageInfo = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'))
139+
const entryModuleId = npmResolver.normalizeModuleId(path.resolve(moduleId, packageInfo.main))
140+
for (const ext of ['.js', '.ts']) {
141+
if (fs.existsSync(path.join(tempDir, `${entryModuleId}${ext}`))) {
142+
return entryModuleId
143+
}
144+
}
145+
}
146+
147+
return null
148+
})
149+
150+
expect(result).toBe('/pages/feature/miniprogram_npm/westore/local')
151+
})
152+
})
153+
116154
describe('isValidComponent', () => {
117155
it('应该验证有效的组件', () => {
118156
const componentPath = path.join(tempDir, 'test-component')
@@ -180,4 +218,4 @@ describe('NpmResolver', () => {
180218
expect(npmResolver.packageCache.size).toBe(0)
181219
})
182220
})
183-
})
221+
})

fe/packages/compiler/__tests__/require-path-resolution.spec.js

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,134 @@ describe('Require Path Resolution', () => {
181181
expect(compiledCode).not.toMatch(/require\(['"]@vant/)
182182
})
183183

184+
it('should resolve bare npm package requires via package entry', async () => {
185+
fs.mkdirSync('pages/npm-entry', { recursive: true })
186+
fs.mkdirSync('miniprogram_npm/westore', { recursive: true })
187+
188+
fs.writeFileSync('app.json', JSON.stringify({
189+
pages: ['pages/npm-entry/index']
190+
}))
191+
192+
fs.writeFileSync('miniprogram_npm/westore/package.json', JSON.stringify({
193+
main: 'index.js'
194+
}))
195+
196+
fs.writeFileSync('miniprogram_npm/westore/index.js', `
197+
module.exports = {
198+
create: function() {
199+
return 'westore'
200+
}
201+
}
202+
`)
203+
204+
fs.writeFileSync('pages/npm-entry/index.js', `
205+
const westore = require('westore')
206+
207+
Page({
208+
onLoad() {
209+
console.log(westore.create())
210+
}
211+
})
212+
`)
213+
214+
fs.writeFileSync('pages/npm-entry/index.json', JSON.stringify({
215+
navigationBarTitleText: 'NPM Entry'
216+
}))
217+
218+
storeInfo(tempDir)
219+
220+
const progress = { completedTasks: 0 }
221+
const result = await compileJS([{ path: 'pages/npm-entry/index' }], null, null, progress)
222+
223+
const modulePaths = result.map(module => module.path)
224+
expect(modulePaths).toContain('/miniprogram_npm/westore/index')
225+
226+
const pageModule = result.find(module => module.path === 'pages/npm-entry/index')
227+
expect(pageModule.code).toContain('require("/miniprogram_npm/westore/index")')
228+
})
229+
230+
it('should resolve npm directory requires to index modules', async () => {
231+
fs.mkdirSync('pages/npm-dir', { recursive: true })
232+
fs.mkdirSync('miniprogram_npm/@vant/weapp/util', { recursive: true })
233+
234+
fs.writeFileSync('app.json', JSON.stringify({
235+
pages: ['pages/npm-dir/index']
236+
}))
237+
238+
fs.writeFileSync('miniprogram_npm/@vant/weapp/util/index.js', `
239+
exports.getName = function() {
240+
return 'vant-util'
241+
}
242+
`)
243+
244+
fs.writeFileSync('pages/npm-dir/index.js', `
245+
const { getName } = require('@vant/weapp/util')
246+
247+
Page({
248+
onLoad() {
249+
console.log(getName())
250+
}
251+
})
252+
`)
253+
254+
fs.writeFileSync('pages/npm-dir/index.json', JSON.stringify({
255+
navigationBarTitleText: 'NPM Dir'
256+
}))
257+
258+
storeInfo(tempDir)
259+
260+
const progress = { completedTasks: 0 }
261+
const result = await compileJS([{ path: 'pages/npm-dir/index' }], null, null, progress)
262+
263+
const modulePaths = result.map(module => module.path)
264+
expect(modulePaths).toContain('/miniprogram_npm/@vant/weapp/util/index')
265+
266+
const pageModule = result.find(module => module.path === 'pages/npm-dir/index')
267+
expect(pageModule.code).toContain('require("/miniprogram_npm/@vant/weapp/util/index")')
268+
})
269+
270+
it('should prefer the nearest miniprogram_npm directory for bare package requires', async () => {
271+
fs.mkdirSync('pages/near/index/miniprogram_npm/westore', { recursive: true })
272+
fs.mkdirSync('miniprogram_npm/westore', { recursive: true })
273+
274+
fs.writeFileSync('app.json', JSON.stringify({
275+
pages: ['pages/near/index/index']
276+
}))
277+
278+
fs.writeFileSync('pages/near/index/miniprogram_npm/westore/package.json', JSON.stringify({
279+
main: 'local.js'
280+
}))
281+
fs.writeFileSync('pages/near/index/miniprogram_npm/westore/local.js', 'module.exports = "local"')
282+
283+
fs.writeFileSync('miniprogram_npm/westore/package.json', JSON.stringify({
284+
main: 'root.js'
285+
}))
286+
fs.writeFileSync('miniprogram_npm/westore/root.js', 'module.exports = "root"')
287+
288+
fs.writeFileSync('pages/near/index/index.js', `
289+
const westore = require('westore')
290+
291+
Page({
292+
onLoad() {
293+
console.log(westore)
294+
}
295+
})
296+
`)
297+
298+
fs.writeFileSync('pages/near/index/index.json', JSON.stringify({
299+
navigationBarTitleText: 'Nearest NPM'
300+
}))
301+
302+
storeInfo(tempDir)
303+
304+
const progress = { completedTasks: 0 }
305+
const result = await compileJS([{ path: 'pages/near/index/index' }], null, null, progress)
306+
307+
const pageModule = result.find(module => module.path === 'pages/near/index/index')
308+
expect(pageModule.code).toContain('require("/pages/near/index/miniprogram_npm/westore/local")')
309+
expect(pageModule.code).not.toContain('require("/miniprogram_npm/westore/root")')
310+
})
311+
184312
it('should handle mixed import and require statements', async () => {
185313
// 创建测试文件结构
186314
fs.mkdirSync('pages/mixed', { recursive: true })

fe/packages/compiler/src/common/npm-resolver.js

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,29 @@ class NpmResolver {
6767
return null
6868
}
6969

70+
/**
71+
* 解析脚本模块路径,支持微信小程序 npm 逐级寻址和 package.json 入口
72+
* @param {string} specifier 模块导入路径
73+
* @param {string} modulePath 当前模块绝对文件路径
74+
* @param {(moduleId: string) => string | null} resolveExistingModuleId 解析真实存在模块的回调
75+
* @returns {string|null} 解析后的模块 id
76+
*/
77+
resolveScriptModule(specifier, modulePath, resolveExistingModuleId) {
78+
if (!specifier || !resolveExistingModuleId) {
79+
return null
80+
}
81+
82+
for (const searchPath of this.generateSearchPaths(modulePath)) {
83+
const moduleId = this.normalizeModuleId(`/${searchPath}/${specifier}`)
84+
const resolvedModuleId = resolveExistingModuleId(moduleId)
85+
if (resolvedModuleId) {
86+
return resolvedModuleId
87+
}
88+
}
89+
90+
return null
91+
}
92+
7093
/**
7194
* 生成 miniprogram_npm 搜索路径
7295
* 按照微信小程序的寻址顺序生成搜索路径
@@ -132,6 +155,14 @@ class NpmResolver {
132155
return null
133156
}
134157

158+
normalizeModuleId(moduleId) {
159+
let normalized = moduleId.replace(/\.(js|ts)$/, '').replace(/\\/g, '/')
160+
if (!normalized.startsWith('/')) {
161+
normalized = `/${normalized}`
162+
}
163+
return normalized
164+
}
165+
135166
/**
136167
* 检查是否为有效的组件
137168
* @param {string} componentPath 组件路径
@@ -219,4 +250,4 @@ class NpmResolver {
219250
}
220251
}
221252

222-
export { NpmResolver }
253+
export { NpmResolver }

0 commit comments

Comments
 (0)