Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions packages/vite/src/node/__tests__/plugins/completeAmdWrap.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { describe, expect, test } from 'vitest'
import { completeAmdWrapPlugin } from '../../plugins/completeAmdWrap'

async function createCompleteAmdWrapPluginRenderChunk() {
const instance = completeAmdWrapPlugin()

return async (code: string) => {
// @ts-expect-error transform.handler should exist
const result = await instance.renderChunk.call(instance, code, 'foo.ts', {
format: 'amd',
})
return result?.code || result
}
}

describe('completeAmdWrapPlugin', async () => {
const renderChunk = await createCompleteAmdWrapPluginRenderChunk()

describe('adds require parameter', async () => {
test('without other dependencies', async () => {
expect(
await renderChunk('define((function() { } ))'),
).toMatchInlineSnapshot(`"define(["require"], (function(require) { } ))"`)
})

test('with other dependencies', async () => {
expect(
await renderChunk(
'define(["vue", "vue-router"], function(vue, vueRouter) { } ))',
),
).toMatchInlineSnapshot(
`"define(["require", "vue", "vue-router"], (function(require, vue, vueRouter) { } ))"`,
)
})

test("only if require isn't injected already", async () => {
expect(
await renderChunk('define(["require"], function(require) { } ))'),
).toMatchInlineSnapshot(`"define(["require"], (function(require) { } ))"`)

expect(
await renderChunk(`define(['require'], function(require) { } ))`),
).toMatchInlineSnapshot(`"define(['require'], (function(require) { } ))"`)
})
})
})
2 changes: 2 additions & 0 deletions packages/vite/src/node/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ import {
resolveChokidarOptions,
resolveEmptyOutDir,
} from './watch'
import { completeAmdWrapPlugin } from './plugins/completeAmdWrap'
import { completeSystemWrapPlugin } from './plugins/completeSystemWrap'
import { webWorkerPostPlugin } from './plugins/worker'
import { getHookHandler } from './plugins'
Expand Down Expand Up @@ -477,6 +478,7 @@ export async function resolveBuildPlugins(config: ResolvedConfig): Promise<{
}> {
return {
pre: [
completeAmdWrapPlugin(),
completeSystemWrapPlugin(),
...(!config.isWorker ? [prepareOutDirPlugin()] : []),
perEnvironmentPlugin('commonjs', (environment) => {
Expand Down
30 changes: 30 additions & 0 deletions packages/vite/src/node/plugins/completeAmdWrap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type { Plugin } from '../plugin'

/**
* ensure amd bundles request `require` to be injected
*/
export function completeAmdWrapPlugin(): Plugin {
const AmdWrapRE =
/\bdefine\((?:\s*\[([^\]]*)\],)?\s*(?:\(\s*)?function\s*\(([^)]*)\)\s*\{/g

return {
name: 'vite:force-amd-wrap-require',
renderChunk(code, _chunk, opts) {
if (opts.format !== 'amd') return

return {
code: code.replace(AmdWrapRE, (_, deps, params) => {
if (deps?.includes(`"require"`) || deps?.includes(`'require'`)) {
return `define([${deps}], (function(${params}) {`
}

const newDeps = deps ? `"require", ${deps}` : '"require"'
const newParams = params.trim() ? `require, ${params}` : 'require'

return `define([${newDeps}], (function(${newParams}) {`
}),
map: null, // no need to generate sourcemap as no mapping exists for the wrapper
}
},
}
}
17 changes: 17 additions & 0 deletions playground/amd/__tests__/amd.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { describe, expect, test } from 'vitest'
import { browserLogs, getBg, isBuild, page } from '~utils'

describe.runIf(isBuild)('build', () => {
test('should have no 404s', async () => {
await page.waitForLoadState('networkidle')
browserLogs.forEach((msg) => {
expect(msg).not.toMatch('404')
})
})

test('asset url is correct with `base: "."`', async () => {
await expect
.poll(() => getBg('.assets'))
.toMatch(/\/assets\/asset-[-\w]{8}\.png/)
})
})
Binary file added playground/amd/asset.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions playground/amd/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import asset from './asset.png'

export default function pluginMain() {
return asset
}
10 changes: 10 additions & 0 deletions playground/amd/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"requirejs": "^2.3.7"
}
}
30 changes: 30 additions & 0 deletions playground/amd/public/index.html
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason this file is in public/ and not in the root?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is because this file is not part of the build (the entrypoint is index.ts) and won't be copied to dist if it's in the root.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright, but it feels a bit weird to be building a Vite app this way. Usually the build output format doesn't affect dev, but I suppose it's fine as a test setup for now.

Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>amd</title>
</head>
<body>
<h1>AMD</h1>
<p>An image should be shown below</p>
<div
class="assets"
style="background-size: cover; height: 100px; width: 100px"
></div>

<script src="/npm/requirejs.js"></script>
<script type="text/javascript">
requirejs(
['assets/plugin.js'],
(plugin) => {
const url = plugin()
document.querySelector('.assets').style.backgroundImage =
`url(${url})`
},
(err) => {
console.error('Error loading plugin', err)
},
)
</script>
</body>
</html>
53 changes: 53 additions & 0 deletions playground/amd/vite.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import fs from 'node:fs/promises'
import path from 'node:path'
import { type Connect, defineConfig } from 'vite'

export default defineConfig({
base: './',
build: {
outDir: 'dist/nested',
rollupOptions: {
preserveEntrySignatures: 'strict',
input: {
plugin: path.resolve(import.meta.dirname, './index.ts'),
},
output: {
format: 'amd',
entryFileNames: 'assets/[name].js',
},
},
},
plugins: [
{
name: 'serve-npm-code-directly',
configureServer({ middlewares }) {
middlewares.use(serveNpmCodeDirectlyMiddleware)
},
configurePreviewServer({ middlewares }) {
middlewares.use(serveNpmCodeDirectlyMiddleware)
},
},
],
appType: 'mpa', // to cause 404 for incorrect URLs
})

const npmDirectServeConfig = {
'/npm/requirejs.js': 'requirejs/require.js',
}
const serveNpmCodeDirectlyMiddleware: Connect.NextHandleFunction = async (
req,
res,
next,
) => {
for (const [url, file] of Object.entries(npmDirectServeConfig)) {
if (req.originalUrl === url) {
const code = await fs.readFile(
new URL(`./node_modules/${file}`, import.meta.url),
)
res.setHeader('Content-Type', 'text/javascript')
res.end(code)
return
}
}
next()
}
13 changes: 13 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.