Compare commits

...

15 Commits

Author SHA1 Message Date
GitHub Actions 36be4ce771 chore(release): @wxt-dev/module-solid v1.1.0 2024-07-02 15:37:46 +00:00
KnightYoshi 18f8a387b0 feat: add eslint 9 config support (#762)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2024-07-02 10:32:32 -05:00
C.C 48c2c07557 docs: Fix manifest typo (#787) 2024-07-02 07:09:07 -05:00
Aaron 61548a9478 fix(dev): Catch error when attempting to reload a tab in a saved tab group (#786) 2024-07-01 16:11:00 -05:00
dependabot[bot] fd678e9572 chore(deps): bump oven-sh/setup-bun from 1 to 2 (#785)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-01 15:40:10 -05:00
Aaron 63fb913f76 docs: New example search (#751) 2024-06-30 09:01:42 -05:00
Aaron faffe20a62 chore(deps): Upgrade non-major deps (#778) 2024-06-29 15:00:15 -05:00
Aaron 3ed8bd10fd chore: Upgrade buildc to v1.0.10 (#780) 2024-06-29 14:09:56 -05:00
Asakura Mizu 6f3f270573 fix: Respect custom outDir when cleaning and zipping (#774) 2024-06-28 09:05:40 -05:00
Aaron 5347cb4626 chore: Replace consola with wxt.logger (#776) 2024-06-28 09:04:21 -05:00
Aaron d6b4d2d1f6 chore(dev-deps): Upgrade buildc to v1.0.9 2024-06-27 16:47:35 -05:00
Aaron 0d7ae287da chore(dev-deps): Upgrade buildc to v1.0.8 2024-06-27 16:38:40 -05:00
Aaron 90cb3bd3bf feat: Enable auto-imports for JSX/TSX files (#773) 2024-06-27 00:05:45 -05:00
Prakhar 🔱 Man of Creativity 60eb97c4b2 docs: Update examples URL (#768)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2024-06-26 13:52:10 -05:00
Aaron 0ffc6eae3e Fix comment typo 2024-06-25 15:12:59 -05:00
62 changed files with 1373 additions and 436 deletions
+1 -1
View File
@@ -33,7 +33,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup
- uses: oven-sh/setup-bun@v1
- uses: oven-sh/setup-bun@v2
- name: pnpm test:coverage
run: pnpm test:coverage --reporter=default --reporter=hanging-process
- uses: codecov/codecov-action@v4
@@ -1,34 +0,0 @@
<script lang="ts" setup>
import { ref, onMounted, computed } from 'vue';
const props = defineProps<{
tag?: string;
}>();
const examples = ref();
onMounted(async () => {
const res = await fetch(
'https://raw.githubusercontent.com/wxt-dev/wxt-examples/main/examples.json',
);
examples.value = await res.json();
});
const filteredExamples = computed(() => {
if (props.tag == null) return examples.value;
return examples.value.filter((example) => {
return example.tags?.includes(props.tag);
});
});
</script>
<template>
<ul>
<li v-if="examples == null">Loading...</li>
<template v-else>
<li v-for="example of filteredExamples">
<a :href="example.url" target="_blank">{{ example.name }}</a>
</li>
</template>
</ul>
</template>
@@ -0,0 +1,226 @@
<script lang="ts" setup>
import { ref, onMounted, computed, toRaw, Ref } from 'vue';
import ExampleSearchFilterByItem from './ExampleSearchFilterByItem.vue';
import ExampleSearchResult from './ExampleSearchResult.vue';
import { ExamplesMetadata, KeySelectedObject } from '../utils/types';
const props = defineProps<{
tag?: string;
}>();
const exampleMetadata = ref<ExamplesMetadata>();
onMounted(async () => {
const res = await fetch(
'https://raw.githubusercontent.com/wxt-dev/examples/main/metadata.json',
);
exampleMetadata.value = await res.json();
});
const searchText = ref('');
const selectedApis = ref<KeySelectedObject>({});
const selectedPermissions = ref<KeySelectedObject>({});
const selectedPackages = ref<KeySelectedObject>({});
function useRequiredItems(selectedItems: Ref<KeySelectedObject>) {
return computed(() =>
Array.from(
Object.entries(toRaw(selectedItems.value)).reduce(
(set, [pkg, checked]) => {
if (checked) set.add(pkg);
return set;
},
new Set<string>(),
),
),
);
}
const requiredApis = useRequiredItems(selectedApis);
const requiredPermissions = useRequiredItems(selectedPermissions);
const requiredPackages = useRequiredItems(selectedPackages);
function doesExampleMatchSelected(
exampleItems: string[],
requiredItems: Ref<string[]>,
) {
const exampleItemsSet = new Set(exampleItems);
return !requiredItems.value.find((item) => !exampleItemsSet.has(item));
}
const filteredExamples = computed(() => {
const text = searchText.value.toLowerCase();
return exampleMetadata.value.examples.filter((example) => {
const matchesText = example.searchText.toLowerCase().includes(text);
const matchesApis = doesExampleMatchSelected(example.apis, requiredApis);
const matchesPermissions = doesExampleMatchSelected(
example.permissions,
requiredPermissions,
);
const matchesPackages = doesExampleMatchSelected(
example.packages,
requiredPackages,
);
return matchesText && matchesApis && matchesPermissions && matchesPackages;
});
});
</script>
<template>
<div class="example-layout">
<div class="search">
<input v-model="searchText" placeholder="Search for an example..." />
</div>
<div class="filters">
<ExampleSearchFilterByItem
label="APIs"
:items="exampleMetadata?.allApis"
v-model="selectedApis"
/>
<ExampleSearchFilterByItem
label="Permissions"
:items="exampleMetadata?.allPermissions"
v-model="selectedPermissions"
/>
<ExampleSearchFilterByItem
label="Packages"
:items="exampleMetadata?.allPackages"
v-model="selectedPackages"
/>
</div>
<div class="results">
<p v-if="exampleMetadata == null">Loading examples...</p>
<template v-else>
<ul class="search-results">
<ExampleSearchResult
v-for="example of filteredExamples"
:key="example.name"
:example
/>
</ul>
<p v-if="filteredExamples.length === 0">No matching examples</p>
</template>
</div>
</div>
</template>
<style scoped>
.example-layout {
display: grid;
grid-template-columns: 1fr;
grid-template-rows: 1fr;
grid-template-areas:
'search'
'results';
gap: 16px;
}
@media only screen and (min-width: 720px) {
.example-layout {
grid-template-columns: 256px 1fr;
grid-template-rows: auto 1fr;
grid-template-areas:
'filters search'
'filters results';
}
}
.search {
grid-area: search;
background: var(--vp-c-bg-soft);
padding: 20px;
width: 100%;
display: flex;
border-radius: 16px;
}
.filters {
display: none;
grid-area: filters;
}
@media only screen and (min-width: 720px) {
.filters {
display: flex;
flex-direction: column;
gap: 2px;
border-radius: 16px;
overflow: hidden;
align-self: flex-start;
}
}
.results {
grid-area: results;
}
.box {
border-radius: 16px;
overflow: hidden;
}
.search input {
min-width: 0;
flex: 1;
font-size: 16px;
}
.checkbox-col {
flex: 1;
padding: 16px;
display: flex;
flex-direction: column;
overflow-y: auto;
max-height: 200px;
font-size: 14px;
gap: 4px;
}
.filter-btn {
color: var(--vp-c-brand-1);
}
.checkbox-col .header {
font-size: 12px;
font-weight: bold;
opacity: 50%;
}
.checkbox-col p {
display: flex;
gap: 4px;
align-items: flex-start;
text-wrap: wrap;
overflow-wrap: anywhere;
line-height: 140%;
}
span {
padding-top: 1px;
}
.checkbox-col input[type='checkbox'] {
width: 16px;
height: 16px;
flex-shrink: 0;
}
.checkbox-col-container {
display: flex;
}
.search-results {
display: grid;
grid-template-columns: repeat(1, 1fr);
gap: 16px;
}
@media only screen and (min-width: 800px) {
.search-results {
grid-template-columns: repeat(2, 1fr);
}
}
@media only screen and (min-width: 1024px) {
.search-results {
grid-template-columns: repeat(3, 1fr);
}
}
a {
background-color: red;
}
</style>
@@ -0,0 +1,100 @@
<script lang="ts" setup>
import { computed, toRaw } from 'vue';
import { KeySelectedObject } from '../utils/types';
const props = defineProps<{
label: string;
items?: string[];
}>();
const selectedItems = defineModel<KeySelectedObject>({
required: true,
});
const count = computed(() => {
return Object.values(toRaw(selectedItems.value)).filter(Boolean).length;
});
function toggleItem(pkg: string) {
selectedItems.value = {
...toRaw(selectedItems.value),
[pkg]: !selectedItems.value[pkg],
};
}
</script>
<template>
<div class="filter-container">
<p class="header">
<span>Filter by {{ label }}</span> <span v-if="count">({{ count }})</span>
</p>
<div class="scroll-container">
<ul>
<li v-for="item in items">
<label :title="item">
<input
type="checkbox"
:checked="selectedItems[item]"
@input="toggleItem(item)"
/>
<span>{{ item }}</span>
</label>
</li>
</ul>
</div>
</div>
</template>
<style scoped>
.filter-container {
height: 300px;
display: flex;
flex-direction: column;
background: var(--vp-c-bg-soft);
}
.scroll-container {
flex: 1;
overflow: hidden;
position: relative;
}
.scroll-container ul {
position: absolute;
overflow-y: auto;
left: 0;
top: 0;
right: 0;
bottom: 0;
display: flex;
flex-direction: column;
gap: 4px;
font-size: small;
padding: 8px 16px 16px 16px;
}
.header {
padding: 8px 16px;
font-size: 12px;
font-weight: bold;
opacity: 50%;
}
label {
display: flex;
gap: 4px;
align-items: flex-start;
text-wrap: wrap;
overflow-wrap: anywhere;
line-height: 140%;
cursor: pointer;
text-wrap: nowrap;
}
span {
padding-top: 1px;
}
input[type='checkbox'] {
width: 16px;
height: 16px;
flex-shrink: 0;
}
</style>
@@ -0,0 +1,72 @@
<script lang="ts" setup>
import { Example } from '../utils/types';
const props = defineProps<{
example: Example;
}>();
</script>
<template>
<li>
<a :href="example.url" target="_blank">
<p class="name">{{ example.name }}</p>
<p class="description">{{ example.description }}</p>
<p class="link">Open &rarr;</p>
</a>
</li>
</template>
<style scoped>
* {
min-width: 0;
}
a {
padding: 16px;
display: flex;
flex-direction: column;
border: 2px solid var(--vp-c-bg-soft);
border-radius: 16px;
color: var(--vp-c-text-1) !important;
gap: 8px;
}
a:hover {
outline: 2px solid var(--vp-c-brand-2);
}
.name {
font-size: 16px;
font-weight: 500;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
flex-shrink: 0;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.description {
height: 53px;
opacity: 70%;
font-size: 14px;
font-weight: normal;
line-height: 120%;
min-height: 0;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
.link {
opacity: 0;
transition: 250ms;
color: var(--vp-c-brand-2);
font-weight: bold;
text-align: right;
}
a:hover .link {
opacity: 100%;
}
</style>
-1
View File
@@ -78,7 +78,6 @@ export default defineConfig({
menuItem('Configuration', 'configuration'),
menuItem('Entrypoints', 'entrypoints'),
menuItem('Assets', 'assets'),
menuItem('Testing', 'testing'),
menuItem('Publishing', 'publishing'),
menuItem('Migrate to WXT', 'migrate-to-wxt'),
menuItem('Compare', 'compare'),
+2 -2
View File
@@ -2,7 +2,7 @@ import DefaultTheme from 'vitepress/theme';
import Icon from '../components/Icon.vue';
import EntrypointPatterns from '../components/EntrypointPatterns.vue';
import UsingWxtSection from '../components/UsingWxtSection.vue';
import ExampleList from '../components/ExampleList.vue';
import ExampleSearch from '../components/ExampleSearch.vue';
import './custom.css';
export default {
@@ -11,6 +11,6 @@ export default {
ctx.app.component('Icon', Icon);
ctx.app.component('EntrypointPatterns', EntrypointPatterns);
ctx.app.component('UsingWxtSection', UsingWxtSection);
ctx.app.component('ExampleList', ExampleList);
ctx.app.component('ExampleSearch', ExampleSearch);
},
};
+18
View File
@@ -0,0 +1,18 @@
export interface Example {
name: string;
description?: string;
url: string;
searchText: string;
apis: string[];
permissions: string[];
packages: string[];
}
export type ExamplesMetadata = {
examples: Example[];
allApis: string[];
allPermissions: string[];
allPackages: string[];
};
export type KeySelectedObject = Record<string, boolean | undefined>;
+16 -4
View File
@@ -1,7 +1,19 @@
# Examples
---
layout: page
---
Simple walkthroughs to accomplish common tasks or patterns with WXT.
<style>
.examples-container {
padding: 32px;
}
</style>
<ExampleList />
<div class="examples-container">
<div class="vp-doc">
<h1>Examples</h1>
</div>
> Full code available at [`wxt-dev/wxt-examples`](https://github.com/wxt-dev/wxt-examples)
<br />
<ExampleSearch />
</div>
+1 -1
View File
@@ -113,4 +113,4 @@ You're ready to build your web extension!
- Read the rest of the "Get Started" pages for a high-overview of what WXT can do
- Read the [Guide](/guide/key-concepts/manifest) to learn in-depth about each feature WXT supports
- [Configure WXT](./configuration) by creating a `wxt.config.ts` file
- Checkout [example projects](https://github.com/wxt-dev/wxt-examples) to see how to perform common tasks with WXT
- Checkout [example projects](https://github.com/wxt-dev/examples) to see how to perform common tasks with WXT
-25
View File
@@ -1,25 +0,0 @@
# Testing
## Official Frameworks
WXT officially supports [Vitest](https://vitest.dev/) for unit tests and either [Playwright](https://playwright.dev/) or [Puppeteer](https://pptr.dev/) for E2E tests against Chromium browsers.
For details setting up each testing framework, see the official examples:
<ExampleList tag="testing" />
## Unofficial Frameworks
Puppeteer and Playwright are the only E2E test runners that support Chrome Extensions. There are no other options at the time of writing.
There are other options for unit tests however, like [Jest](https://jestjs.io/), [Mocha](https://mochajs.org/), or [`node:test`](https://nodejs.org/api/test.html). **_WXT does not claim to support any of them_** because none of them support all of WXT's features, like TypeScript or auto-imports.
If you want to try to use a different framework for unit tests, you will need to configure the environment manually:
- **Auto-imports**: Add `unimport` to your test environment or disable them by setting `imports: false` in your `wxt.config.ts` file
- **`browser` mock**: Mock the `webextension-polyfill` module globally with `wxt/dist/virtual/mock-browser.js`
- **[Remote Code Bundling](/guide/go-further/remote-code)**: If you use it, configure your environment to handle the `url:` module prefix
- **Global Variables**: If you consume them, manually define globals provided by WXT (like `import.meta.env.BROWSER`) by adding them to the global scope before accessing them (`import.meta.env.BROWSER = "chrome"`)
- **Import paths**: If you use the `@/` or `~/` path aliases, add them to your test environment
[Here's how Vitest is configured](https://github.com/wxt-dev/wxt/blob/main/packages/wxt/src/testing/wxt-vitest-plugin.ts) for reference.
@@ -37,4 +37,4 @@ Chrome extensions allow you to add panels and side panes to the devtools window.
See the WXT's examples for a full walkthrough of extending the devtools window:
<ExampleList tag="devtools" />
- [Devtools Setup](https://github.com/wxt-dev/wxt-examples/tree/main/examples/vanilla-devtools#readme)
+3 -1
View File
@@ -6,7 +6,9 @@ WXT officially supports [Vitest](https://vitest.dev/) for unit tests and either
For details setting up each testing framework, see the official examples:
<ExampleList tag="testing" />
- [Vitest](https://github.com/wxt-dev/wxt-examples/tree/main/examples/vanilla-vitest#readme)
- [Playwright](https://github.com/wxt-dev/wxt-examples/tree/main/examples/vanilla-playwright#readme)
- [Puppeteer](https://github.com/wxt-dev/wxt-examples/tree/main/examples/vanilla-puppeteer#readme)
### Unofficial Frameworks
+27 -10
View File
@@ -79,24 +79,41 @@ export default defineConfig({
## ESLint
ESLint doesn't understand auto-imports; it thinks all auto-imported variables are undeclared globals and will report lint errors for each. To fix this, extend the ESLint file generated inside the `.wxt` directory:
By default, ESLint thinks auto-imported variables are not defined. You have to add them to the `globals` config so it knows these variables exist at runtime.
```js
// .eslintrc.js
module.exports = {
extends: ['./.wxt/eslintrc-auto-import.json'],
};
```
By default, this file will be generated when ESLint is a direct dependency. If ESLint is a subdependency or your project is a monorepo, it may not be generated automatically. In this case, you can tell WXT to generate it:
WXT generates a config file containing the globals you can extend! By default, WXT will attempt to detect if ESLint is installed in your project and generate the config file if so. If the config file isn't being generated automatically, you can manually tell WXT to generate it:
```ts
// wxt.config.ts
export default defineConfig({
imports: {
eslintrc: {
enabled: true,
enabled: 8, // Generate ESLint v8 compatible config
// or
enabled: 9, // Generate ESLint v9 compatible config
},
},
});
```
### ESLint 9 and above
WXT supports the new "flat config" file format. Just import the generated file and add it to the array of config to extend.
```js
// eslint.config.mjs
import autoImports from './.wxt/eslint-auto-imports.mjs';
export default [autoImports];
```
### ESLint 8 and below
Just extend the generated file:
```js
// .eslintrc.mjs
export default {
extends: ['./.wxt/eslintrc-auto-import.json'],
};
```
+3 -2
View File
@@ -25,7 +25,7 @@ Here's an example `wxt.config.ts` file:
import { defineConfig } from 'wxt';
export default defineConfig({
mainfest: {
manifest: {
action: {
default_title: 'Some Title',
},
@@ -176,7 +176,8 @@ export default defineConfig({
See the official localization examples for more details:
<ExampleList tag="i18n" />
- [I18n](https://github.com/wxt-dev/wxt-examples/tree/main/examples/vanilla-i18n#readme)
- [Vue I18n](https://github.com/wxt-dev/wxt-examples/tree/main/examples/vue-i18n#readme)
## Actions
+5 -5
View File
@@ -18,14 +18,14 @@
"docs:preview": "pnpm -s docs:gen && vitepress preview docs"
},
"devDependencies": {
"@aklinker1/buildc": "^1.0.7",
"@aklinker1/buildc": "^1.0.10",
"@aklinker1/check": "^1.3.1",
"@types/fs-extra": "^11.0.4",
"@vitest/coverage-v8": "^1.6.0",
"changelogen": "^0.5.5",
"consola": "^3.2.3",
"dependency-graph": "^1.0.0",
"execa": "^9.2.0",
"execa": "^9.3.0",
"fast-glob": "^3.3.2",
"fs-extra": "^11.2.0",
"hasha": "^6.0.0",
@@ -33,16 +33,16 @@
"npm-run-all": "^4.1.5",
"prettier": "^3.3.2",
"simple-git-hooks": "^2.11.1",
"tsx": "^4.15.4",
"tsx": "4.15.7",
"typedoc": "^0.25.4",
"typedoc-plugin-markdown": "4.0.0-next.23",
"typedoc-vitepress-theme": "1.0.0-next.3",
"typescript": "^5.4.5",
"typescript": "^5.5.2",
"vitepress": "^1.2.3",
"vitest": "^1.6.0",
"vitest-mock-extended": "^1.3.1",
"vitest-plugin-random-seed": "^1.1.0",
"vue": "^3.4.27",
"vue": "^3.4.31",
"wxt": "workspace:*",
"yaml": "^2.4.5"
},
+12
View File
@@ -0,0 +1,12 @@
import { defineBuildConfig } from 'unbuild';
import { resolve } from 'node:path';
export default defineBuildConfig({
rootDir: 'modules',
outDir: resolve(__dirname, 'dist'),
entries: [{ input: 'react.ts', name: 'index' }],
rollup: {
emitCJS: true,
},
declaration: true,
});
+5
View File
@@ -0,0 +1,5 @@
export default function () {
const [count, setCount] = useState(0);
const increment = () => setCount((count) => count + 1);
return <button onClick={increment}>Count: {count}</button>;
}
@@ -0,0 +1,33 @@
import { defineContentScript } from 'wxt/sandbox';
import { ContentScriptContext, createShadowRootUi } from 'wxt/client';
import React from 'react';
import ReactDOM from 'react-dom/client';
export default defineContentScript({
matches: ['*://*/*'],
async main(ctx) {
const ui = await createUi(ctx);
ui.mount();
},
});
function createUi(ctx: ContentScriptContext) {
return createShadowRootUi(ctx, {
name: 'react-ui',
position: 'inline',
append: 'first',
onMount(container) {
const root = ReactDOM.createRoot(container);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>,
);
return root;
},
onRemove(root) {
root?.unmount();
},
});
}
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="./main.tsx"></script>
</body>
</html>
@@ -0,0 +1,10 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
const root = document.getElementById('app')!;
ReactDOM.createRoot(root).render(
<React.StrictMode>
<App />
</React.StrictMode>,
);
@@ -13,6 +13,17 @@ export default defineWxtModule<ReactModuleOptions>({
}));
addImportPreset(wxt, 'react');
// Enable auto-imports for JSX files
wxt.hooks.hook('ready', (wxt) => {
if (wxt.config.imports === false) return;
wxt.config.imports.dirsScanOptions ??= {};
wxt.config.imports.dirsScanOptions.filePatterns = [
// Default plus JSX/TSX
'*.{ts,js,mjs,cjs,mts,cts,jsx,tsx}',
];
});
},
});
+8 -2
View File
@@ -21,8 +21,10 @@
"dist"
],
"scripts": {
"dev": "wxt",
"check": "buildc --deps-only -- check",
"build": "buildc -- unbuild",
"check": "buildc --deps-only -- check"
"postinstall": "buildc --deps-only -- wxt prepare"
},
"peerDependencies": {
"wxt": ">=0.18.6"
@@ -32,8 +34,12 @@
},
"devDependencies": {
"@aklinker1/check": "^1.3.1",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"publint": "^0.2.8",
"typescript": "^5.4.5",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"typescript": "^5.5.2",
"unbuild": "^2.0.0",
"wxt": "workspace:*"
}
View File
+5 -1
View File
@@ -1,4 +1,8 @@
{
"extends": "../../tsconfig.base.json",
"extends": ["../../tsconfig.base.json", "./.wxt/tsconfig.json"],
"compilerOptions": {
"allowImportingTsExtensions": true,
"jsx": "react-jsx"
},
"exclude": ["node_modules/**", "dist/**"]
}
+17
View File
@@ -1,5 +1,22 @@
# Changelog
## v1.1.0
[compare changes](https://github.com/wxt-dev/wxt/compare/module-solid-v1.0.1...module-solid-v1.1.0)
### 🚀 Enhancements
- Enable auto-imports for JSX/TSX files ([#773](https://github.com/wxt-dev/wxt/pull/773))
### 🩹 Fixes
- Upgrade wxt peer to >= 0.18.6 ([7edf1c8](https://github.com/wxt-dev/wxt/commit/7edf1c8))
### 🏡 Chore
- Extract build cache script to NPM package ([#737](https://github.com/wxt-dev/wxt/pull/737))
- **deps:** Upgrade non-major deps ([#778](https://github.com/wxt-dev/wxt/pull/778))
## v1.0.1
[compare changes](https://github.com/wxt-dev/wxt/compare/module-solid-v1.0.0...module-solid-v1.0.1)
+12
View File
@@ -0,0 +1,12 @@
import { defineBuildConfig } from 'unbuild';
import { resolve } from 'node:path';
export default defineBuildConfig({
rootDir: 'modules',
outDir: resolve(__dirname, 'dist'),
entries: [{ input: 'solid.ts', name: 'index' }],
rollup: {
emitCJS: true,
},
declaration: true,
});
+7
View File
@@ -0,0 +1,7 @@
import { Component } from 'solid-js';
export const App: Component = () => {
const [count, setCount] = createSignal(0);
const increment = () => setCount((count) => count + 1);
return <button onClick={increment}>Count: {count()}</button>;
};
@@ -0,0 +1,26 @@
import { defineContentScript } from 'wxt/sandbox';
import { ContentScriptContext, createShadowRootUi } from 'wxt/client';
import { render } from 'solid-js/web';
export default defineContentScript({
matches: ['*://*/*'],
async main(ctx) {
const ui = await createUi(ctx);
ui.mount();
},
});
function createUi(ctx: ContentScriptContext) {
return createShadowRootUi(ctx, {
name: 'solid-ui',
position: 'inline',
append: 'first',
onMount(container) {
return render(() => <App />, container);
},
onRemove(unmount) {
unmount?.();
},
});
}
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="./main.tsx"></script>
</body>
</html>
@@ -0,0 +1,5 @@
import { render } from 'solid-js/web';
const root = document.getElementById('app')!;
render(() => <App />, root);
@@ -16,6 +16,17 @@ export default defineWxtModule<SolidModuleOptions>({
}));
addImportPreset(wxt, 'solid-js');
// Enable auto-imports for JSX files
wxt.hooks.hook('ready', (wxt) => {
if (wxt.config.imports === false) return;
wxt.config.imports.dirsScanOptions ??= {};
wxt.config.imports.dirsScanOptions.filePatterns = [
// Default plus JSX/TSX
'*.{ts,js,mjs,cjs,mts,cts,jsx,tsx}',
];
});
},
});
+6 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@wxt-dev/module-solid",
"version": "1.0.1",
"version": "1.1.0",
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.mjs",
@@ -21,8 +21,10 @@
"dist"
],
"scripts": {
"dev": "wxt",
"check": "buildc --deps-only -- check",
"build": "buildc -- unbuild",
"check": "buildc --deps-only -- check"
"postinstall": "buildc --deps-only -- wxt prepare"
},
"peerDependencies": {
"wxt": ">=0.18.6"
@@ -33,7 +35,8 @@
"devDependencies": {
"@aklinker1/check": "^1.3.1",
"publint": "^0.2.8",
"typescript": "^5.4.5",
"solid-js": "^1.8.17",
"typescript": "^5.5.2",
"unbuild": "^2.0.0",
"wxt": "workspace:*"
}
View File
+5 -1
View File
@@ -1,4 +1,8 @@
{
"extends": "../../tsconfig.base.json",
"extends": ["../../tsconfig.base.json", "./.wxt/tsconfig.json"],
"compilerOptions": {
"jsx": "preserve",
"jsxImportSource": "solid-js"
},
"exclude": ["node_modules/**", "dist/**"]
}
+1 -1
View File
@@ -33,7 +33,7 @@
"devDependencies": {
"@aklinker1/check": "^1.3.1",
"publint": "^0.2.8",
"typescript": "^5.4.5",
"typescript": "^5.5.2",
"unbuild": "^2.0.0",
"wxt": "workspace:*"
}
+1 -1
View File
@@ -33,7 +33,7 @@
"devDependencies": {
"@aklinker1/check": "^1.3.1",
"publint": "^0.2.8",
"typescript": "^5.4.5",
"typescript": "^5.5.2",
"unbuild": "^2.0.0",
"wxt": "workspace:*"
}
+12
View File
@@ -0,0 +1,12 @@
import autoImports from './.wxt/eslintrc-auto-import.js';
export default [
{
languageOptions: {
globals: {
...autoImports.globals,
},
sourceType: 'module',
},
},
];
+2 -2
View File
@@ -23,8 +23,8 @@
"devDependencies": {
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"sass": "^1.77.5",
"typescript": "^5.4.5",
"sass": "^1.77.6",
"typescript": "^5.5.2",
"wxt": "workspace:*"
},
"buildc": {
@@ -0,0 +1,102 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`Auto Imports > eslintrc > "enabled: 8" should output a JSON config file compatible with ESlint 8 1`] = `
".wxt/eslintrc-auto-import.json
----------------------------------------
{
"globals": {
"ContentScriptContext": true,
"InvalidMatchPattern": true,
"MatchPattern": true,
"browser": true,
"createIframeUi": true,
"createIntegratedUi": true,
"createShadowRootUi": true,
"defineBackground": true,
"defineConfig": true,
"defineContentScript": true,
"defineUnlistedScript": true,
"defineWxtPlugin": true,
"fakeBrowser": true,
"storage": true
}
}
"
`;
exports[`Auto Imports > eslintrc > "enabled: 9" should output a flat config file compatible with ESlint 9 1`] = `
".wxt/eslint-auto-imports.mjs
----------------------------------------
const globals = {
"ContentScriptContext": true,
"InvalidMatchPattern": true,
"MatchPattern": true,
"browser": true,
"createIframeUi": true,
"createIntegratedUi": true,
"createShadowRootUi": true,
"defineBackground": true,
"defineConfig": true,
"defineContentScript": true,
"defineUnlistedScript": true,
"defineWxtPlugin": true,
"fakeBrowser": true,
"storage": true
}
export default {
languageOptions: {
globals,
sourceType: "module",
},
};
"
`;
exports[`Auto Imports > eslintrc > "enabled: true" should output a JSON config file compatible with ESlint 8 1`] = `
".wxt/eslintrc-auto-import.json
----------------------------------------
{
"globals": {
"ContentScriptContext": true,
"InvalidMatchPattern": true,
"MatchPattern": true,
"browser": true,
"createIframeUi": true,
"createIntegratedUi": true,
"createShadowRootUi": true,
"defineBackground": true,
"defineConfig": true,
"defineContentScript": true,
"defineUnlistedScript": true,
"defineWxtPlugin": true,
"fakeBrowser": true,
"storage": true
}
}
"
`;
exports[`Auto Imports > eslintrc > should allow customizing the output 1`] = `
"example.json
----------------------------------------
{
"globals": {
"ContentScriptContext": "readonly",
"InvalidMatchPattern": "readonly",
"MatchPattern": "readonly",
"browser": "readonly",
"createIframeUi": "readonly",
"createIntegratedUi": "readonly",
"createShadowRootUi": "readonly",
"defineBackground": "readonly",
"defineConfig": "readonly",
"defineContentScript": "readonly",
"defineUnlistedScript": "readonly",
"defineWxtPlugin": "readonly",
"fakeBrowser": "readonly",
"storage": "readonly"
}
}
"
`;
+155 -49
View File
@@ -1,5 +1,6 @@
import { describe, it, expect } from 'vitest';
import { TestProject } from '../utils';
import { execaCommand } from 'execa';
describe('Auto Imports', () => {
describe('imports: { ... }', () => {
@@ -96,7 +97,7 @@ describe('Auto Imports', () => {
});
describe('eslintrc', () => {
it('should output the globals list for ESLint to consume', async () => {
it('"enabled: true" should output a JSON config file compatible with ESlint 8', async () => {
const project = new TestProject();
project.addFile('entrypoints/popup.html', `<html></html>`);
@@ -108,30 +109,43 @@ describe('Auto Imports', () => {
},
});
expect(await project.serializeFile('.wxt/eslintrc-auto-import.json'))
.toMatchInlineSnapshot(`
".wxt/eslintrc-auto-import.json
----------------------------------------
{
"globals": {
"ContentScriptContext": true,
"InvalidMatchPattern": true,
"MatchPattern": true,
"browser": true,
"createIframeUi": true,
"createIntegratedUi": true,
"createShadowRootUi": true,
"defineBackground": true,
"defineConfig": true,
"defineContentScript": true,
"defineUnlistedScript": true,
"defineWxtPlugin": true,
"fakeBrowser": true,
"storage": true
}
}
"
`);
expect(
await project.serializeFile('.wxt/eslintrc-auto-import.json'),
).toMatchSnapshot();
});
it('"enabled: 8" should output a JSON config file compatible with ESlint 8', async () => {
const project = new TestProject();
project.addFile('entrypoints/popup.html', `<html></html>`);
await project.prepare({
imports: {
eslintrc: {
enabled: 8,
},
},
});
expect(
await project.serializeFile('.wxt/eslintrc-auto-import.json'),
).toMatchSnapshot();
});
it('"enabled: 9" should output a flat config file compatible with ESlint 9', async () => {
const project = new TestProject();
project.addFile('entrypoints/popup.html', `<html></html>`);
await project.prepare({
imports: {
eslintrc: {
enabled: 9,
},
},
});
expect(
await project.serializeFile('.wxt/eslint-auto-imports.mjs'),
).toMatchSnapshot();
});
it('should allow customizing the output', async () => {
@@ -148,30 +162,122 @@ describe('Auto Imports', () => {
},
});
expect(await project.serializeFile('example.json'))
.toMatchInlineSnapshot(`
"example.json
----------------------------------------
{
"globals": {
"ContentScriptContext": "readonly",
"InvalidMatchPattern": "readonly",
"MatchPattern": "readonly",
"browser": "readonly",
"createIframeUi": "readonly",
"createIntegratedUi": "readonly",
"createShadowRootUi": "readonly",
"defineBackground": "readonly",
"defineConfig": "readonly",
"defineContentScript": "readonly",
"defineUnlistedScript": "readonly",
"defineWxtPlugin": "readonly",
"fakeBrowser": "readonly",
"storage": "readonly"
}
}
"
`);
expect(await project.serializeFile('example.json')).toMatchSnapshot();
});
describe('Actual linting results', () => {
async function runEslint(
project: TestProject,
version: boolean | 'auto' | 8 | 9,
) {
project.addFile(
'entrypoints/background.js',
`export default defineBackground(() => {})`,
);
await project.prepare({
imports: { eslintrc: { enabled: version } },
});
return await execaCommand('pnpm eslint entrypoints/background.js', {
cwd: project.root,
});
}
describe('ESLint 9', () => {
it('should have lint errors when not extending generated config', async () => {
const project = new TestProject({
devDependencies: {
'@eslint/js': '9.5.0',
eslint: '9.5.0',
},
});
project.addFile(
'eslint.config.mjs',
`
import eslint from "@eslint/js";
export default [
eslint.configs.recommended,
];
`,
);
await expect(runEslint(project, 9)).rejects.toMatchObject({
message: expect.stringContaining(
"'defineBackground' is not defined",
),
});
});
it('should not have any lint errors when configured', async () => {
const project = new TestProject({
devDependencies: {
'@eslint/js': '9.5.0',
eslint: '9.5.0',
},
});
project.addFile(
'eslint.config.mjs',
`
import eslint from "@eslint/js";
import autoImports from "./.wxt/eslint-auto-imports.mjs";
export default [
eslint.configs.recommended,
autoImports,
];
`,
);
const res = await runEslint(project, 9);
expect(res).toBeDefined();
});
});
describe('ESLint 8', () => {
it('should have lint errors when not extending generated config', async () => {
const project = new TestProject({
devDependencies: {
eslint: '8.57.0',
},
});
project.addFile(
'.eslintrc',
JSON.stringify({
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
env: { es6: true },
extends: ['eslint:recommended'],
}),
);
await expect(runEslint(project, 8)).rejects.toMatchObject({
message: expect.stringContaining(
"'defineBackground' is not defined",
),
});
});
it('should not have any lint errors when configured', async () => {
const project = new TestProject({
devDependencies: {
eslint: '8.57.0',
},
});
project.addFile(
'.eslintrc',
JSON.stringify({
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
env: { es6: true },
extends: [
'eslint:recommended',
'./.wxt/eslintrc-auto-import.json',
],
}),
);
const res = await runEslint(project, 8);
expect(res).toBeDefined();
});
});
});
});
});
+8 -8
View File
@@ -124,12 +124,12 @@
"jiti": "^1.21.6",
"json5": "^2.2.3",
"jszip": "^3.10.1",
"linkedom": "^0.18.3",
"linkedom": "^0.18.4",
"magicast": "^0.3.4",
"minimatch": "^9.0.4",
"minimatch": "^9.0.5",
"natural-compare": "^1.4.0",
"normalize-path": "^3.0.0",
"nypm": "^0.3.8",
"nypm": "^0.3.9",
"ohash": "^1.1.3",
"open": "^10.1.0",
"ora": "^7.0.1",
@@ -137,7 +137,7 @@
"prompts": "^2.4.2",
"publish-browser-extension": "^2.1.3",
"unimport": "^3.7.2",
"vite": "^5.3.0",
"vite": "^5.3.2",
"vite-node": "^1.6.0",
"web-ext-run": "^0.2.0",
"webextension-polyfill": "^0.10.0"
@@ -148,17 +148,17 @@
"@types/fs-extra": "^11.0.4",
"@types/lodash.merge": "^4.6.9",
"@types/natural-compare": "^1.4.3",
"@types/node": "^20.14.2",
"@types/node": "^20.14.9",
"@types/normalize-path": "^3.0.2",
"@types/prompts": "^2.4.9",
"execa": "^9.2.0",
"execa": "^9.3.0",
"extract-zip": "^2.0.1",
"happy-dom": "^13.10.1",
"lodash.merge": "^4.6.2",
"p-map": "^7.0.2",
"publint": "^0.2.8",
"tsup": "^8.1.0",
"tsx": "^4.15.4",
"typescript": "^5.4.5"
"tsx": "4.15.7",
"typescript": "^5.5.2"
}
}
+40 -13
View File
@@ -30,8 +30,8 @@ export default defineWxtModule({
wxt.config.userModules.forEach(addModuleImports);
});
// Create unimport instance after READY so any modifications to the config
// inside "ready" are applied.
// Create unimport instance AFTER "ready" so any modifications to the
// config inside "ready" are applied.
wxt.hooks.afterEach((event) => {
if (event.name === 'ready') {
unimport = createUnimport(options);
@@ -45,8 +45,10 @@ export default defineWxtModule({
entries.push(await getImportsDeclarationEntry(unimport));
if (!options.eslintrc.enabled) return;
entries.push(await getImportsEslintEntry(unimport, options));
if (options.eslintrc.enabled === false) return;
entries.push(
await getEslintConfigEntry(unimport, options.eslintrc.enabled, options),
);
});
// Add vite plugin
@@ -100,23 +102,48 @@ async function getImportsDeclarationEntry(
};
}
async function getImportsEslintEntry(
async function getEslintConfigEntry(
unimport: Unimport,
version: 8 | 9,
options: WxtResolvedUnimportOptions,
): Promise<WxtDirFileEntry> {
const globals: Record<string, EslintGlobalsPropValue> = {};
const eslintrc = { globals };
(await unimport.getImports())
const globals = (await unimport.getImports())
.map((i) => i.as ?? i.name)
.filter(Boolean)
.sort()
.forEach((name) => {
eslintrc.globals[name] = options.eslintrc.globalsPropValue;
});
.reduce<Record<string, EslintGlobalsPropValue>>((globals, name) => {
globals[name] = options.eslintrc.globalsPropValue;
return globals;
}, {});
if (version <= 8) return getEslint8ConfigEntry(options, globals);
else return getEslint9ConfigEntry(options, globals);
}
export function getEslint8ConfigEntry(
options: WxtResolvedUnimportOptions,
globals: Record<string, EslintGlobalsPropValue>,
): WxtDirFileEntry {
return {
path: options.eslintrc.filePath,
text: JSON.stringify(eslintrc, null, 2) + '\n',
text: JSON.stringify({ globals }, null, 2) + '\n',
};
}
export function getEslint9ConfigEntry(
options: WxtResolvedUnimportOptions,
globals: Record<string, EslintGlobalsPropValue>,
): WxtDirFileEntry {
return {
path: options.eslintrc.filePath,
text: `const globals = ${JSON.stringify(globals, null, 2)}
export default {
languageOptions: {
globals,
sourceType: "module",
},
};
`,
};
}
+20 -2
View File
@@ -345,14 +345,32 @@ describe('CLI', () => {
mockArgv('clean');
await importCli();
expect(cleanMock).toBeCalledWith(undefined);
expect(cleanMock).toBeCalledWith({});
});
it('should respect passing a custom root', async () => {
mockArgv('clean', 'path/to/root');
await importCli();
expect(cleanMock).toBeCalledWith('path/to/root');
expect(cleanMock).toBeCalledWith({ root: 'path/to/root' });
});
it('should respect a custom config file', async () => {
mockArgv('clean', '-c', './path/to/config.ts');
await importCli();
expect(cleanMock).toBeCalledWith({
configFile: './path/to/config.ts',
});
});
it('should respect passing --debug', async () => {
mockArgv('clean', '--debug');
await importCli();
expect(cleanMock).toBeCalledWith({
debug: true,
});
});
});
+2 -1
View File
@@ -126,9 +126,10 @@ cli
cli
.command('clean [root]', 'clean generated files and caches')
.alias('cleanup')
.option('-c, --config <file>', 'use specified config file')
.action(
wrapAction(async (root, flags) => {
await clean(root);
await clean({ root, configFile: flags.config, debug: flags.debug });
}),
);
+4 -1
View File
@@ -244,7 +244,10 @@ export async function createViteBuilder(
const config = vite.mergeConfig(baseConfig, envConfig);
const server = await vite.createServer(config);
await server.pluginContainer.buildStart({});
const node = new ViteNodeServer(server);
const node = new ViteNodeServer(
// @ts-ignore: Some weird type error...
server,
);
installSourcemapsSupport({
getSourceMap: (source) => node.getSourceMap(source),
});
+30 -9
View File
@@ -1,44 +1,65 @@
import path from 'node:path';
import glob from 'fast-glob';
import fs from 'fs-extra';
import { consola } from 'consola';
import pc from 'picocolors';
import { InlineConfig } from '~/types';
import { registerWxt, wxt } from './wxt';
/**
* Remove generated/temp files from the directory.
*
* @param config Optional config that will override your `<root>/wxt.config.ts`.
*
* @example
* await clean();
*/
export async function clean(config?: InlineConfig): Promise<void>;
/**
* Remove generated/temp files from the directory.
*
* @deprecated
*
* @param root The directory to look for generated/temp files in. Defaults to `process.cwd()`. Can be relative to `process.cwd()` or absolute.
*
* @example
* await clean();
*/
export async function clean(root = process.cwd()) {
consola.info('Cleaning Project');
export async function clean(root?: string): Promise<void>;
export async function clean(config?: string | InlineConfig) {
if (typeof config === 'string') {
config = { root: config };
}
await registerWxt('build', config);
wxt.logger.info('Cleaning Project');
const root = wxt.config.root;
const tempDirs = [
'node_modules/.vite',
'node_modules/.cache',
'**/.wxt',
'.output/*',
`${path.relative(root, wxt.config.outBaseDir)}/*`,
];
consola.debug('Looking for:', tempDirs.map(pc.cyan).join(', '));
wxt.logger.debug('Looking for:', tempDirs.map(pc.cyan).join(', '));
const directories = await glob(tempDirs, {
cwd: path.resolve(root),
cwd: root,
absolute: true,
onlyDirectories: true,
deep: 2,
});
if (directories.length === 0) {
consola.debug('No generated files found.');
wxt.logger.debug('No generated files found.');
return;
}
consola.debug(
wxt.logger.debug(
'Found:',
directories.map((dir) => pc.cyan(path.relative(root, dir))).join(', '),
);
for (const directory of directories) {
await fs.rm(directory, { force: true, recursive: true });
consola.debug('Deleted ' + pc.cyan(path.relative(root, directory)));
wxt.logger.debug('Deleted ' + pc.cyan(path.relative(root, directory)));
}
}
+3 -4
View File
@@ -20,7 +20,6 @@ import {
findEntrypoints,
} from '~/core/utils/building';
import { createExtensionRunner } from '~/core/runners';
import { consola } from 'consola';
import { Mutex } from 'async-mutex';
import pc from 'picocolors';
import { relative } from 'node:path';
@@ -201,21 +200,21 @@ function createFileReloader(server: WxtDevServer) {
switch (changes.type) {
case 'extension-reload':
server.reloadExtension();
consola.success(`Reloaded extension`);
wxt.logger.success(`Reloaded extension`);
break;
case 'html-reload':
const { reloadedNames } = reloadHtmlPages(
changes.rebuildGroups,
server,
);
consola.success(`Reloaded: ${getFilenameList(reloadedNames)}`);
wxt.logger.success(`Reloaded: ${getFilenameList(reloadedNames)}`);
break;
case 'content-script-reload':
reloadContentScripts(changes.changedSteps, server);
const rebuiltNames = changes.rebuildGroups
.flat()
.map((entry) => entry.name);
consola.success(`Reloaded: ${getFilenameList(rebuiltNames)}`);
wxt.logger.success(`Reloaded: ${getFilenameList(rebuiltNames)}`);
break;
}
} catch (err) {
@@ -19,8 +19,7 @@ export const bun: WxtPackageManagerImpl = {
.slice(1) // Skip the first line, is not a dependency
.map((line) => line.trim())
.map((line) => /.* (@?\S+)@(\S+)$/.exec(line))
// @ts-expect-error: Filtering to known non-null matches
.filter<RegExpExecArray>((match) => !!match)
.filter((match) => !!match)
.map(([_, name, version]) => ({ name, version })),
);
},
@@ -15,7 +15,6 @@ import {
ValidationResults,
validateEntrypoints,
} from '../validation';
import consola from 'consola';
import { wxt } from '../../wxt';
import { mergeJsonOutputs } from '@aklinker1/rollup-plugin-visualizer';
import { isCI } from 'ci-info';
@@ -134,12 +133,12 @@ function printValidationResults({
}, new Map<Entrypoint, ValidationResult[]>());
Array.from(entrypointErrors.entries()).forEach(([entrypoint, errors]) => {
consola.log(relative(cwd, entrypoint.inputPath));
wxt.logger.log(relative(cwd, entrypoint.inputPath));
console.log();
errors.forEach((err) => {
const type = err.type === 'error' ? pc.red('ERROR') : pc.yellow('WARN');
const recieved = pc.dim(`(recieved: ${JSON.stringify(err.value)})`);
consola.log(` - ${type} ${err.message} ${recieved}`);
wxt.logger.log(` - ${type} ${err.message} ${recieved}`);
});
console.log();
});
@@ -12,17 +12,19 @@ import {
WxtCommand,
WxtModule,
WxtModuleWithMetadata,
ResolvedEslintrc,
Eslintrc,
} from '~/types';
import path from 'node:path';
import { createFsCache } from '~/core/utils/cache';
import consola, { LogLevels } from 'consola';
import defu from 'defu';
import { NullablyRequired } from '../types';
import { isModuleInstalled } from '../package';
import fs from 'fs-extra';
import { normalizePath } from '../paths';
import glob from 'fast-glob';
import { builtinModules } from '~/builtin-modules';
import { getEslintVersion } from '../eslint';
/**
* Given an inline config, discover the config file if necessary, merge the results, resolve any
@@ -167,7 +169,7 @@ export async function resolveConfig(
srcDir,
typesDir,
wxtDir,
zip: resolveZipConfig(root, mergedConfig),
zip: resolveZipConfig(root, outBaseDir, mergedConfig),
transformManifest: mergedConfig.transformManifest,
analysis: resolveAnalysisConfig(root, mergedConfig),
userConfigMetadata: userConfigMetadata ?? {},
@@ -241,6 +243,7 @@ async function mergeInlineConfig(
function resolveZipConfig(
root: string,
outBaseDir: string,
mergedConfig: InlineConfig,
): NullablyRequired<ResolvedConfig['zip']> {
const downloadedPackagesDir = path.resolve(root, '.wxt/local_modules');
@@ -261,6 +264,8 @@ function resolveZipConfig(
// Tests
'**/__tests__/**',
'**/*.+(test|spec).?(c|m)+(j|t)s?(x)',
// Output directory
`${path.relative(root, outBaseDir)}/**`,
// From user
...(mergedConfig.zip?.excludeSources ?? []),
],
@@ -299,17 +304,6 @@ async function getUnimportOptions(
): Promise<WxtResolvedUnimportOptions | false> {
if (config.imports === false) return false;
const rawEslintEnabled = config.imports?.eslintrc?.enabled;
let eslintEnabled: boolean;
switch (rawEslintEnabled) {
case undefined:
case 'auto':
eslintEnabled = await isModuleInstalled('eslint');
break;
default:
eslintEnabled = rawEslintEnabled;
}
const defaultOptions: WxtResolvedUnimportOptions = {
debugLog: logger.debug,
imports: [
@@ -327,11 +321,7 @@ async function getUnimportOptions(
dirsScanOptions: {
cwd: srcDir,
},
eslintrc: {
enabled: eslintEnabled,
filePath: path.resolve(wxtDir, 'eslintrc-auto-import.json'),
globalsPropValue: true,
},
eslintrc: await getUnimportEslintOptions(wxtDir, config.imports?.eslintrc),
};
return defu<WxtResolvedUnimportOptions, [WxtResolvedUnimportOptions]>(
@@ -340,6 +330,40 @@ async function getUnimportOptions(
);
}
async function getUnimportEslintOptions(
wxtDir: string,
options: Eslintrc | undefined,
): Promise<ResolvedEslintrc> {
const rawEslintEnabled = options?.enabled ?? 'auto';
let eslintEnabled: ResolvedEslintrc['enabled'];
switch (rawEslintEnabled) {
case 'auto':
const version = await getEslintVersion();
let major = parseInt(version[0]);
if (major <= 8) eslintEnabled = 8;
else if (major >= 9) eslintEnabled = 9;
// NaN
else eslintEnabled = 8;
break;
case true:
eslintEnabled = 8;
break;
default:
eslintEnabled = rawEslintEnabled;
}
return {
enabled: eslintEnabled,
filePath: path.resolve(
wxtDir,
eslintEnabled === 9
? 'eslint-auto-imports.mjs'
: 'eslintrc-auto-import.json',
),
globalsPropValue: true,
};
}
/**
* Returns the path to `node_modules/wxt`.
*/
+11
View File
@@ -0,0 +1,11 @@
export async function getEslintVersion(): Promise<string[]> {
try {
const require = (await import('node:module')).default.createRequire(
import.meta.url,
);
const { ESLint } = require('eslint');
return ESLint.version?.split('.') ?? [];
} catch (error) {
return [];
}
}
-4
View File
@@ -20,7 +20,3 @@ export async function getPackageJson(): Promise<
return {};
}
}
export function isModuleInstalled(name: string) {
return import(/* @vite-ignore */ name).then(() => true).catch(() => false);
}
@@ -245,7 +245,7 @@ export const fakeResolvedConfig = fakeObjectCreator<ResolvedConfig>(() => {
fsCache: mock<FsCache>(),
imports: {
eslintrc: {
enabled: faker.datatype.boolean(),
enabled: faker.helpers.arrayElement([false, 8, 9]),
filePath: fakeFile(),
globalsPropValue: faker.helpers.arrayElement([
true,
+11 -6
View File
@@ -1238,17 +1238,21 @@ export interface Eslintrc {
/**
* When true, generates a file that can be used by ESLint to know which variables are valid globals.
*
* - `'auto'`: Check if eslint is installed, and if it is, generate the helper file
* - `true`: Generate the helper file
* - `false`: Don't generate the file
* - `false`: Don't generate the file.
* - `'auto'`: Check if eslint is installed, and if it is, generate a compatible config file.
* - `true`: Same as `8`.
* - `8`: Generate a config file compatible with ESLint 8.
* - `9`: Generate a config file compatible with ESLint 9.
*
* @default 'auto'
*/
enabled?: boolean | 'auto';
enabled?: false | true | 'auto' | 8 | 9;
/**
* File path to save the generated eslint config.
*
* @default './.wxt/eslintrc-auto-import.json'
* Default depends on version of ESLint used:
* - 9 and above: './.wxt/eslint-auto-imports.mjs'
* - 8 and below: './.wxt/eslintrc-auto-import.json'
*/
filePath?: string;
/**
@@ -1258,7 +1262,8 @@ export interface Eslintrc {
}
export interface ResolvedEslintrc {
enabled: boolean;
/** False if disabled, otherwise the major version of ESLint installed */
enabled: false | 8 | 9;
/** Absolute path */
filePath: string;
globalsPropValue: EslintGlobalsPropValue;
@@ -81,7 +81,15 @@ async function reloadTabsForContentScript(contentScript: ContentScript) {
if (!url) return false;
return !!matchPatterns.find((pattern) => pattern.includes(url));
});
await Promise.all(matchingTabs.map((tab) => browser.tabs.reload(tab.id)));
await Promise.all(
matchingTabs.map(async (tab) => {
try {
await browser.tabs.reload(tab.id);
} catch (err) {
logger.warn('Failed to reload tab:', err);
}
}),
);
}
export async function reloadContentScriptMv2(
+230 -202
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -22,7 +22,7 @@
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@wxt-dev/module-react": "^1.0.0",
"typescript": "^5.4.5",
"wxt": "^0.18.6"
"typescript": "^5.5.2",
"wxt": "^0.18.10"
}
}
+3 -3
View File
@@ -18,8 +18,8 @@
"solid-js": "^1.8.17"
},
"devDependencies": {
"typescript": "^5.4.5",
"@wxt-dev/module-solid": "^1.0.0",
"wxt": "^0.18.6"
"@wxt-dev/module-solid": "^1.0.1",
"typescript": "^5.5.2",
"wxt": "^0.18.10"
}
}
+3 -3
View File
@@ -17,9 +17,9 @@
"@tsconfig/svelte": "^5.0.4",
"@wxt-dev/module-svelte": "^1.0.0",
"svelte": "^4.2.18",
"svelte-check": "^3.8.0",
"svelte-check": "^3.8.4",
"tslib": "^2.6.3",
"typescript": "^5.4.5",
"wxt": "^0.18.6"
"typescript": "^5.5.2",
"wxt": "^0.18.10"
}
}
+2 -2
View File
@@ -15,7 +15,7 @@
"postinstall": "wxt prepare"
},
"devDependencies": {
"typescript": "^5.4.5",
"wxt": "^0.18.6"
"typescript": "^5.5.2",
"wxt": "^0.18.10"
}
}
+4 -4
View File
@@ -15,12 +15,12 @@
"postinstall": "wxt prepare"
},
"dependencies": {
"vue": "^3.4.27"
"vue": "^3.4.31"
},
"devDependencies": {
"@wxt-dev/module-vue": "^1.0.0",
"typescript": "^5.4.5",
"vue-tsc": "^2.0.21",
"wxt": "^0.18.6"
"typescript": "^5.5.2",
"vue-tsc": "^2.0.22",
"wxt": "^0.18.10"
}
}