feat: Windows support (#50)

This commit is contained in:
Aaron
2023-07-20 22:54:30 -05:00
committed by GitHub
parent 2c70246af5
commit 488d7885ca
15 changed files with 83 additions and 33 deletions
+2 -2
View File
@@ -7,7 +7,7 @@ jobs:
uses: './.github/workflows/validate.yml'
publish:
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
needs:
- validate
steps:
@@ -31,7 +31,7 @@ jobs:
run: |
pnpm install --ignore-scripts
pnpm build
pnpm install
pnpm install --ignore-scripts
- name: Bump and Tag
run: |
+8 -3
View File
@@ -9,7 +9,11 @@ on:
jobs:
wxt:
name: WXT
runs-on: ubuntu-20.04
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
uses: actions/checkout@v3
@@ -29,7 +33,8 @@ jobs:
run: |
pnpm install --ignore-scripts
pnpm build
pnpm install
pnpm install --ignore-scripts
cd demo && pnpm wxt prepare
- name: Formatting
run: pnpm format:check
@@ -52,7 +57,7 @@ jobs:
project-templates:
name: Project Templates
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v3
+6
View File
@@ -103,6 +103,12 @@ Finally, add scripts to your `package.json`:
> You can skip `*:firefox` scripts if you don't want to support Firefox
## 🚧 WSL Support
**_WXT does not support [Windows Subsystem for Linux](https://learn.microsoft.com/en-us/windows/wsl/) yet_**. See [Issue #55](https://github.com/aklinker1/wxt/issues/55) to track progress.
In the meantime, you can use `cmd` instead.
## Development
Once you've installed WXT, you can start the development server using the `dev` script.
+2 -1
View File
@@ -3,6 +3,7 @@ import fs from 'fs-extra';
import glob from 'fast-glob';
import { execSync } from 'child_process';
import { InlineConfig, UserConfig, build } from '../src';
import { normalizePath } from '../src/core/utils/paths';
export class TestProject {
files: Array<[string, string]> = [];
@@ -113,7 +114,7 @@ export class TestProject {
async serializeFile(path: string): Promise<string> {
const absolutePath = resolve(this.root, path);
return [
relative(this.root, absolutePath),
normalizePath(relative(this.root, absolutePath)),
await fs.readFile(absolutePath),
].join(`\n${''.padEnd(40, '-')}\n`);
}
@@ -12,6 +12,7 @@ import fs from 'fs-extra';
import { importTsFile } from '../../utils/importTsFile';
import glob from 'fast-glob';
import { fakeInternalConfig } from '../../../testing/fake-objects';
import { unnormalizePath } from '../../utils/paths';
vi.mock('../../utils/importTsFile');
const importTsFileMock = vi.mocked(importTsFile);
@@ -443,9 +444,14 @@ describe('findEntrypoints', () => {
it('should not allow multiple entrypoints with the same name', async () => {
globMock.mockResolvedValueOnce(['popup.html', 'popup/index.html']);
const expectedPaths = [
'src/entrypoints/popup.html',
'src/entrypoints/popup/index.html',
].map(unnormalizePath);
await expect(() => findEntrypoints(config)).rejects.toThrowError(
'Multiple entrypoints with the name "popup" detected, but only one is allowed: src/entrypoints/popup.html, src/entrypoints/popup/index.html',
'Multiple entrypoints with the name "popup" detected, but only one is allowed: ' +
expectedPaths.join(', '),
);
});
});
+12 -15
View File
@@ -6,6 +6,7 @@ import { getEntrypointBundlePath } from '../utils/entrypoints';
import { getUnimportOptions } from '../utils/auto-imports';
import { getGlobals } from '../utils/globals';
import { getPublicFiles } from '../utils/public';
import { normalizePath } from '../utils/paths';
/**
* Generate and write all the files inside the `InternalConfig.typesDir` directory.
@@ -58,6 +59,7 @@ async function writePathsDeclarationFile(
),
)
.concat(await getPublicFiles(config))
.map(normalizePath)
.map((path) => ` | "/${path}"`)
.sort()
.join('\n');
@@ -113,7 +115,8 @@ async function writeMainDeclarationFile(
'// Generated by wxt',
`/// <reference types="vite/client" />`,
...references.map(
(ref) => `/// <reference types="./${relative(dir, ref)}" />`,
(ref) =>
`/// <reference types="./${normalizePath(relative(dir, ref))}" />`,
),
].join('\n') + '\n',
);
@@ -136,32 +139,26 @@ async function writeTsConfigFile(
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
/* Type Checking */
"strict": true,
"lib": ["DOM", "WebWorker"],
/* Completeness */
"skipLibCheck": true,
/* Aliases */
"baseUrl": "${relative(dir, config.root)}",
"baseUrl": "${normalizePath(relative(dir, config.root))}",
"paths": {
"@@": ["."],
"@@/*": ["./*"],
"~~": ["."],
"~~/*": ["./*"],
"@": ["${relative(config.root, config.srcDir)}"],
"@/*": ["${relative(config.root, config.srcDir)}/*"],
"~": ["${relative(config.root, config.srcDir)}"],
"~/*": ["${relative(config.root, config.srcDir)}/*"]
"@": ["${normalizePath(relative(config.root, config.srcDir))}"],
"@/*": ["${normalizePath(relative(config.root, config.srcDir))}/*"],
"~": ["${normalizePath(relative(config.root, config.srcDir))}"],
"~/*": ["${normalizePath(relative(config.root, config.srcDir))}/*"]
}
},
"include": [
"${relative(dir, config.root)}/**/*",
"./${relative(dir, mainReference)}"
"${normalizePath(relative(dir, config.root))}/**/*",
"./${normalizePath(relative(dir, mainReference))}"
],
"exclude": ["${relative(dir, config.outBaseDir)}"]
"exclude": ["${normalizePath(relative(dir, config.outBaseDir))}"]
}`,
);
}
+4 -2
View File
@@ -1,6 +1,7 @@
import { BuildOutput, BuildStepOutput, EntrypointGroup } from '../types';
import * as vite from 'vite';
import { every } from './arrays';
import { normalizePath } from './paths';
/**
* Compare the changed files vs the build output and determine what kind of reload needs to happen:
@@ -102,15 +103,16 @@ function findEffectedSteps(
currentOutput: BuildOutput,
): DetectedChange[] {
const changes: DetectedChange[] = [];
const changedPath = changedFile[1];
const changedPath = normalizePath(changedFile[1]);
const isChunkEffected = (
chunk: vite.Rollup.OutputChunk | vite.Rollup.OutputAsset,
): boolean =>
// If it's an HTML file with the same path, is is effected because HTML files need to be pre-rendered
// TODO: use bundle path to support `<name>/index.html`?
// fileName is normalized, relative bundle path
(chunk.type === 'asset' && changedPath.endsWith(chunk.fileName)) ||
// If it's a chunk that depends on the changed file, it is effected
// moduleIds are absolute, normalized paths
(chunk.type === 'chunk' && chunk.moduleIds.includes(changedPath));
for (const step of currentOutput.steps) {
+6 -3
View File
@@ -1,5 +1,6 @@
import { Entrypoint } from '../types';
import path, { relative, resolve } from 'node:path';
import { normalizePath } from './paths';
export function getEntrypointName(
entrypointsDir: string,
@@ -7,8 +8,8 @@ export function getEntrypointName(
// type: Entrypoint['type'],
): string {
const relativePath = path.relative(entrypointsDir, inputPath);
// Grab the string up to the first . or /
const name = relativePath.split(/[\.\/]/, 2)[0];
// Grab the string up to the first . or / or \\
const name = relativePath.split(/[\.\/\\]/, 2)[0];
return name;
}
@@ -29,5 +30,7 @@ export function getEntrypointBundlePath(
outDir: string,
ext: string,
): string {
return relative(outDir, getEntrypointOutputFile(entrypoint, ext));
return normalizePath(
relative(outDir, getEntrypointOutputFile(entrypoint, ext)),
);
}
+5 -1
View File
@@ -6,6 +6,7 @@ import { resolve } from 'path';
import transform from 'jiti/dist/babel';
import { getUnimportOptions } from './auto-imports';
import { removeImportStatements } from './strings';
import { normalizePath } from './paths';
/**
* Get the value from the default export of a `path`.
@@ -27,6 +28,8 @@ export async function importTsFile<T>(
config: InternalConfig,
): Promise<T> {
config.logger.debug('Loading file metadata:', path);
// JITI & Babel uses normalized paths.
const normalPath = normalizePath(path);
const unimport = createUnimport({
...getUnimportOptions(config),
@@ -53,7 +56,8 @@ export async function importTsFile<T>(
),
},
transform(opts) {
if (opts.filename === path) return transform({ ...opts, source: code });
if (opts.filename === normalPath)
return transform({ ...opts, source: code });
else return transform(opts);
},
});
+17
View File
@@ -0,0 +1,17 @@
import nodePath from 'node:path';
import * as vite from 'vite';
/**
* Converts system paths to normalized bundler path. On windows and unix, this returns paths with /
* instead of \.
*/
export function normalizePath(path: string): string {
return vite.normalizePath(path);
}
/**
* Given a normalized path, convert it to the system path style. On Windows, switch to \, otherwise use /.
*/
export function unnormalizePath(path: string): string {
return nodePath.normalize(path);
}
+3 -1
View File
@@ -1,6 +1,7 @@
import { InternalConfig } from '../types';
import fs from 'fs-extra';
import glob from 'fast-glob';
import { unnormalizePath } from './paths';
/**
* Get all the files in the project's public directory. Returned paths are relative to the
@@ -11,5 +12,6 @@ export async function getPublicFiles(
): Promise<string[]> {
if (!(await fs.exists(config.publicDir))) return [];
return await glob('**/*', { cwd: config.publicDir });
const files = await glob('**/*', { cwd: config.publicDir });
return files.map(unnormalizePath);
}
+3 -2
View File
@@ -3,6 +3,7 @@ import { Entrypoint, InternalConfig } from '../types';
import { dirname, extname, resolve } from 'node:path';
import { getEntrypointBundlePath } from '../utils/entrypoints';
import fs, { ensureDir } from 'fs-extra';
import { normalizePath } from '../utils/paths';
/**
* Ensures the HTML files output by a multipage build are in the correct location. This does two
@@ -23,11 +24,11 @@ export function multipageMove(
name: 'wxt:multipage-move',
async writeBundle(_, bundle) {
for (const oldBundlePath in bundle) {
// oldBundlePath = 'entrypoints/popup.html' or 'entrypoints/o ptions/index.html'
// oldBundlePath = 'entrypoints/popup.html' or 'entrypoints/options/index.html'
// Find a matching entrypoint - oldBundlePath is the same as end end of the input path.
const entrypoint = entrypoints.find(
(entry) => !!entry.inputPath.endsWith(oldBundlePath),
(entry) => !!normalizePath(entry.inputPath).endsWith(oldBundlePath),
);
if (entrypoint == null) {
config.logger.debug('No entrypoint found for', oldBundlePath);
+2 -1
View File
@@ -2,6 +2,7 @@ import { Plugin } from 'vite';
import { Entrypoint, InternalConfig } from '../types';
import fs from 'fs-extra';
import { resolve } from 'path';
import { normalizePath } from '../utils/paths';
/**
* Wraps a user's entrypoint with a vitual version with additional logic.
@@ -21,7 +22,7 @@ export function virtualEntrypoin(
const index = id.indexOf(virtualId);
if (index === -1) return;
const inputPath = id.substring(index + virtualId.length);
const inputPath = normalizePath(id.substring(index + virtualId.length));
return resolvedVirtualId + inputPath;
},
async load(id) {
+5
View File
@@ -28,6 +28,10 @@ export async function build(config: InlineConfig): Promise<BuildOutput> {
return await buildInternal(internalConfig);
}
/**
* Creates a dev server, pre-builds all the files that need to exist to load the extension, and open
* the browser with the extension installed.
*/
export async function createServer(
config?: InlineConfig,
): Promise<WxtDevServer> {
@@ -54,6 +58,7 @@ export async function createServer(
});
server.watcher.on('all', async (event, path, _stats) => {
// Here, "path" is a non-normalized path (ie: C:\\users\\... instead of C:/users/...)
if (path.startsWith(internalConfig.outBaseDir)) return;
changeQueue.push([event, path]);
+1 -1
View File
@@ -9,7 +9,7 @@
/* Type Checking */
"strict": true,
"lib": ["DOM", "WebWorker"],
"lib": ["DOM", "WebWorker", "ESNext"],
/* Linting */
"noUnusedLocals": true,