110 lines
3.7 KiB
TypeScript
110 lines
3.7 KiB
TypeScript
import { BuildOutput, WxtDevServer, InlineConfig } from './core/types';
|
|
import { getInternalConfig } from './core/utils/getInternalConfig';
|
|
import pc from 'picocolors';
|
|
import * as vite from 'vite';
|
|
import { detectDevChanges } from './core/utils/detectDevChanges';
|
|
import { Mutex } from 'async-mutex';
|
|
import { consola } from 'consola';
|
|
import { relative } from 'node:path';
|
|
import { getEntrypointOutputFile } from './core/utils/entrypoints';
|
|
import { buildInternal, rebuild } from './core/build';
|
|
import {
|
|
getServerInfo,
|
|
reloadContentScripts,
|
|
reloadHtmlPages,
|
|
setupServer,
|
|
} from './core/server';
|
|
|
|
export { version } from '../package.json';
|
|
export * from './core/types/external';
|
|
export * from './core/utils/defineConfig';
|
|
export * from './core/utils/defineRunnerConfig';
|
|
|
|
/**
|
|
* Bundles the extension for production. Returns a promise of the build result.
|
|
*/
|
|
export async function build(config: InlineConfig): Promise<BuildOutput> {
|
|
const internalConfig = await getInternalConfig(config, 'build');
|
|
return await buildInternal(internalConfig);
|
|
}
|
|
|
|
export async function createServer(
|
|
config?: InlineConfig,
|
|
): Promise<WxtDevServer> {
|
|
const serverInfo = await getServerInfo();
|
|
|
|
const getLatestInternalConfig = () => {
|
|
const viteConfig: vite.InlineConfig = vite.mergeConfig(
|
|
serverInfo.viteServerConfig,
|
|
config?.vite ?? {},
|
|
);
|
|
return getInternalConfig({ ...config, vite: viteConfig }, 'serve');
|
|
};
|
|
|
|
let internalConfig = await getLatestInternalConfig();
|
|
const server = await setupServer(serverInfo, internalConfig);
|
|
internalConfig.server = server;
|
|
|
|
const fileChangedMutex = new Mutex();
|
|
const changeQueue: Array<[string, string]> = [];
|
|
|
|
server.ws.on('wxt:background-initialized', () => {
|
|
// Register content scripts for the first time since they're not listed in the manifest
|
|
reloadContentScripts(server.currentOutput.steps, internalConfig, server);
|
|
});
|
|
|
|
server.watcher.on('all', async (event, path, _stats) => {
|
|
if (path.startsWith(internalConfig.outBaseDir)) return;
|
|
changeQueue.push([event, path]);
|
|
|
|
await fileChangedMutex.runExclusive(async () => {
|
|
const fileChanges = changeQueue.splice(0, changeQueue.length);
|
|
const changes = detectDevChanges(fileChanges, server.currentOutput);
|
|
|
|
if (changes.type === 'no-change') return;
|
|
|
|
// Log the entrypoints that were effected
|
|
consola.info(
|
|
`Changed: ${Array.from(new Set(fileChanges.map((change) => change[1])))
|
|
.map((file) => pc.dim(relative(internalConfig.root, file)))
|
|
.join(', ')}`,
|
|
);
|
|
const rebuiltNames = changes.rebuildGroups
|
|
.flat()
|
|
.map((entry) => {
|
|
return pc.cyan(
|
|
relative(internalConfig.outDir, getEntrypointOutputFile(entry, '')),
|
|
);
|
|
})
|
|
.join(pc.dim(', '));
|
|
|
|
// Get latest config and Rebuild groups with changes
|
|
internalConfig = await getLatestInternalConfig();
|
|
internalConfig.server = server;
|
|
const { output: newOutput } = await rebuild(
|
|
internalConfig,
|
|
// TODO: this excludes new entrypoints, so they're not built until the dev command is restarted
|
|
changes.rebuildGroups,
|
|
changes.cachedOutput,
|
|
);
|
|
server.currentOutput = newOutput;
|
|
|
|
// Perform reloads
|
|
switch (changes.type) {
|
|
case 'extension-reload':
|
|
server.reloadExtension();
|
|
break;
|
|
case 'html-reload':
|
|
reloadHtmlPages(changes.rebuildGroups, server, internalConfig);
|
|
break;
|
|
case 'content-script-reload':
|
|
reloadContentScripts(changes.changedSteps, internalConfig, server);
|
|
break;
|
|
}
|
|
consola.success(`Reloaded: ${rebuiltNames}`);
|
|
});
|
|
});
|
|
|
|
return server;
|
|
}
|