Compare commits
51 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8c7adbf463 | |||
| 8c4436efe8 | |||
| 5e361c0085 | |||
| ccfc207e92 | |||
| 36be4ce771 | |||
| 18f8a387b0 | |||
| 48c2c07557 | |||
| 61548a9478 | |||
| fd678e9572 | |||
| 63fb913f76 | |||
| faffe20a62 | |||
| 3ed8bd10fd | |||
| 6f3f270573 | |||
| 5347cb4626 | |||
| d6b4d2d1f6 | |||
| 0d7ae287da | |||
| 90cb3bd3bf | |||
| 60eb97c4b2 | |||
| 0ffc6eae3e | |||
| 06a8c5fb0e | |||
| eb86413f35 | |||
| b29d49cbf1 | |||
| c0a0edb30c | |||
| b15a933c75 | |||
| 1851be22a7 | |||
| 39f6c29e4f | |||
| 936e89fbfb | |||
| ad365ecde4 | |||
| b4283e37bd | |||
| b467ad760a | |||
| 9fc6408ef9 | |||
| 394cb1a520 | |||
| ffec88549b | |||
| 4758bea187 | |||
| 6671471f75 | |||
| ae78dc7fb3 | |||
| b62af3d007 | |||
| 43ae44ecd5 | |||
| 5bab9d2f59 | |||
| 7edf1c8103 | |||
| 53b4eac955 | |||
| f9a10c2c52 | |||
| 8206ca9164 | |||
| 1a823cc008 | |||
| efc011eb81 | |||
| 35f6d28977 | |||
| 43fcbec0eb | |||
| a0d56b4d27 | |||
| 38ccb6ace6 | |||
| aaf75f1de1 | |||
| 21e8ca0a2c |
@@ -18,7 +18,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: ./.github/actions/setup
|
- uses: ./.github/actions/setup
|
||||||
- run: pnpm -r --sequential build
|
- run: pnpm buildc all
|
||||||
build-demo:
|
build-demo:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
@@ -33,7 +33,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: ./.github/actions/setup
|
- uses: ./.github/actions/setup
|
||||||
- uses: oven-sh/setup-bun@v1
|
- uses: oven-sh/setup-bun@v2
|
||||||
- name: pnpm test:coverage
|
- name: pnpm test:coverage
|
||||||
run: pnpm test:coverage --reporter=default --reporter=hanging-process
|
run: pnpm test:coverage --reporter=default --reporter=hanging-process
|
||||||
- uses: codecov/codecov-action@v4
|
- uses: codecov/codecov-action@v4
|
||||||
@@ -48,6 +48,7 @@ jobs:
|
|||||||
template:
|
template:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
strategy:
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
template:
|
template:
|
||||||
- react
|
- react
|
||||||
|
|||||||
@@ -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 →</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>
|
||||||
@@ -29,6 +29,7 @@ const chromeExtensionIds = [
|
|||||||
'nedcanggplmbbgmlpcjiafgjcpdimpea', // YTBlock - Block any content from YouTube™
|
'nedcanggplmbbgmlpcjiafgjcpdimpea', // YTBlock - Block any content from YouTube™
|
||||||
'oadbjpccljkplmhnjekgjamejnbadlne', // demo.fun - Interactive product demos that convert
|
'oadbjpccljkplmhnjekgjamejnbadlne', // demo.fun - Interactive product demos that convert
|
||||||
'iopdafdcollfgaoffingmahpffckmjni', // SmartEReply: Elevate Your LinkedIn™ Engagement with AI 🚀📈
|
'iopdafdcollfgaoffingmahpffckmjni', // SmartEReply: Elevate Your LinkedIn™ Engagement with AI 🚀📈
|
||||||
|
'khjdmjcmpolknpccmaaipmidphjokhdf', // WorkFlowy MultiFlow
|
||||||
];
|
];
|
||||||
|
|
||||||
const { data, err, isLoading } = useListExtensionDetails(chromeExtensionIds);
|
const { data, err, isLoading } = useListExtensionDetails(chromeExtensionIds);
|
||||||
|
|||||||
@@ -78,7 +78,6 @@ export default defineConfig({
|
|||||||
menuItem('Configuration', 'configuration'),
|
menuItem('Configuration', 'configuration'),
|
||||||
menuItem('Entrypoints', 'entrypoints'),
|
menuItem('Entrypoints', 'entrypoints'),
|
||||||
menuItem('Assets', 'assets'),
|
menuItem('Assets', 'assets'),
|
||||||
menuItem('Testing', 'testing'),
|
|
||||||
menuItem('Publishing', 'publishing'),
|
menuItem('Publishing', 'publishing'),
|
||||||
menuItem('Migrate to WXT', 'migrate-to-wxt'),
|
menuItem('Migrate to WXT', 'migrate-to-wxt'),
|
||||||
menuItem('Compare', 'compare'),
|
menuItem('Compare', 'compare'),
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import DefaultTheme from 'vitepress/theme';
|
|||||||
import Icon from '../components/Icon.vue';
|
import Icon from '../components/Icon.vue';
|
||||||
import EntrypointPatterns from '../components/EntrypointPatterns.vue';
|
import EntrypointPatterns from '../components/EntrypointPatterns.vue';
|
||||||
import UsingWxtSection from '../components/UsingWxtSection.vue';
|
import UsingWxtSection from '../components/UsingWxtSection.vue';
|
||||||
import ExampleList from '../components/ExampleList.vue';
|
import ExampleSearch from '../components/ExampleSearch.vue';
|
||||||
import './custom.css';
|
import './custom.css';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -11,6 +11,6 @@ export default {
|
|||||||
ctx.app.component('Icon', Icon);
|
ctx.app.component('Icon', Icon);
|
||||||
ctx.app.component('EntrypointPatterns', EntrypointPatterns);
|
ctx.app.component('EntrypointPatterns', EntrypointPatterns);
|
||||||
ctx.app.component('UsingWxtSection', UsingWxtSection);
|
ctx.app.component('UsingWxtSection', UsingWxtSection);
|
||||||
ctx.app.component('ExampleList', ExampleList);
|
ctx.app.component('ExampleSearch', ExampleSearch);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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
@@ -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>
|
||||||
|
|||||||
+26
-26
@@ -4,30 +4,30 @@ Lets compare the features of WXT vs [Plasmo](https://docs.plasmo.com/framework)
|
|||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
| Features | WXT | Plasmo | CRXJS |
|
| Features | WXT | Plasmo | CRXJS |
|
||||||
| ---------------------------------------------------- | :-------------------------: | :--------------------------------------: | :---------------------------------------------------------------------: |
|
| ---------------------------------------------------- | :-------------------------: | :--------------------------------------: | :-------------------------------------: |
|
||||||
| Supports all browsers | ✅ | ✅ | ❌ See [#56](https://github.com/crxjs/chrome-extension-tools/issues/56) |
|
| Supports all browsers | ✅ | ✅ | ✅ As of `v2.0.0-beta.23` |
|
||||||
| MV2 Support | ✅ | ✅ | 🟡 Either MV2 or MV3 |
|
| MV2 Support | ✅ | ✅ | 🟡 Either MV2 or MV3 |
|
||||||
| MV3 Support | ✅ | ✅ | 🟡 Either MV2 or MV3 |
|
| MV3 Support | ✅ | ✅ | 🟡 Either MV2 or MV3 |
|
||||||
| Create Extension ZIPs | ✅ | ✅ | ❌ |
|
| Create Extension ZIPs | ✅ | ✅ | ❌ |
|
||||||
| Create Firefox Sources ZIP | ✅ | ❌ | ❌ |
|
| Create Firefox Sources ZIP | ✅ | ❌ | ❌ |
|
||||||
| First-class TypeScript support | ✅ | ✅ | ✅ |
|
| First-class TypeScript support | ✅ | ✅ | ✅ |
|
||||||
| Entrypoint discovery | File based | File based | ❌ |
|
| Entrypoint discovery | File based | File based | ❌ |
|
||||||
| Inline entrypoint config | ✅ | ✅ | Manifest based |
|
| Inline entrypoint config | ✅ | ✅ | Manifest based |
|
||||||
| Auto-imports | ✅ | ❌ | ❌ |
|
| Auto-imports | ✅ | ❌ | ❌ |
|
||||||
| Supports all frontend frameworks | ✅ | 🟡 Only React, Vue, and Svelte | ✅ |
|
| Supports all frontend frameworks | ✅ | 🟡 Only React, Vue, and Svelte | ✅ |
|
||||||
| Framework specific entrypoints (like `Popup.tsx`) | 🟡 `.html` `.ts` `.tsx` | ✅ `.html` `.ts` `.tsx` `.vue` `.svelte` | ❌ |
|
| Framework specific entrypoints (like `Popup.tsx`) | 🟡 `.html` `.ts` `.tsx` | ✅ `.html` `.ts` `.tsx` `.vue` `.svelte` | ❌ |
|
||||||
| Automated publishing | ✅ | ✅ | ❌ |
|
| Automated publishing | ✅ | ✅ | ❌ |
|
||||||
| Remote Code Bundling (Google Analytics) | ✅ | ✅ | ❌ |
|
| Remote Code Bundling (Google Analytics) | ✅ | ✅ | ❌ |
|
||||||
| <strong style="opacity: 50%">Dev Mode</strong> | | |
|
| <strong style="opacity: 50%">Dev Mode</strong> | | |
|
||||||
| `.env` Files | ✅ | ✅ | ✅ |
|
| `.env` Files | ✅ | ✅ | ✅ |
|
||||||
| Opens browser and install extension | ✅ | ❌ | ❌ |
|
| Opens browser and install extension | ✅ | ❌ | ❌ |
|
||||||
| HMR for UIs | ✅ | 🟡 React only | ✅ |
|
| HMR for UIs | ✅ | 🟡 React only | ✅ |
|
||||||
| Reload HTML Files on Change | ✅ | 🟡 Reloads entire extension | ✅ |
|
| Reload HTML Files on Change | ✅ | 🟡 Reloads entire extension | ✅ |
|
||||||
| Reload Content Scripts on Change | ✅ | 🟡 Reloads entire extension | ✅ |
|
| Reload Content Scripts on Change | ✅ | 🟡 Reloads entire extension | ✅ |
|
||||||
| Reload Background on Change | 🟡 Reloads entire extension | 🟡 Reloads entire extension | 🟡 Reloads entire extension |
|
| Reload Background on Change | 🟡 Reloads entire extension | 🟡 Reloads entire extension | 🟡 Reloads entire extension |
|
||||||
| Respects Content Script `run_at` | ✅ | ✅ | ❌ ESM-style loaders run asynchronously |
|
| Respects Content Script `run_at` | ✅ | ✅ | ❌ ESM-style loaders run asynchronously |
|
||||||
| <strong style="opacity: 50%">Built-in Utils</strong> | | | |
|
| <strong style="opacity: 50%">Built-in Utils</strong> | | | |
|
||||||
| Storage | ✅ | ✅ | ❌ |
|
| Storage | ✅ | ✅ | ❌ |
|
||||||
| Messaging | ❌ | ✅ | ❌ |
|
| Messaging | ❌ | ✅ | ❌ |
|
||||||
| Content Script UI | ✅ | ✅ | ❌ |
|
| Content Script UI | ✅ | ✅ | ❌ |
|
||||||
|
|||||||
@@ -15,7 +15,14 @@ npx wxt@latest init <project-name>
|
|||||||
```
|
```
|
||||||
|
|
||||||
```sh [bun]
|
```sh [bun]
|
||||||
bunx wxt@latest init <project-name>
|
# The "wxt init" command currently fails when ran with bunx.
|
||||||
|
# Use NPX as a workaround, and select "bun" as your package
|
||||||
|
# manager. To stay up to date with this issue, follow
|
||||||
|
# https://github.com/wxt-dev/wxt/issues/707
|
||||||
|
#
|
||||||
|
# bunx wxt@latest init <project-name>
|
||||||
|
|
||||||
|
npx wxt@latest init <project-name>
|
||||||
```
|
```
|
||||||
|
|
||||||
:::
|
:::
|
||||||
@@ -96,7 +103,6 @@ pnpm dev
|
|||||||
```
|
```
|
||||||
|
|
||||||
:::tip 🎉 Well done!
|
:::tip 🎉 Well done!
|
||||||
|
|
||||||
The dev command will build the extension for development, open the browser, and reload the different parts of the extension when you save changes.
|
The dev command will build the extension for development, open the browser, and reload the different parts of the extension when you save changes.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
@@ -107,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 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
|
- 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
|
- [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
|
||||||
|
|||||||
@@ -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:
|
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)
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ Firefox does not support sandboxed pages.
|
|||||||
:patterns="[
|
:patterns="[
|
||||||
['sandbox.html', 'sandbox.html'],
|
['sandbox.html', 'sandbox.html'],
|
||||||
['sandbox/index.html', 'sandbox.html'],
|
['sandbox/index.html', 'sandbox.html'],
|
||||||
['<name>.sandbox.html', '<name>.html` '],
|
['<name>.sandbox.html', '<name>.html'],
|
||||||
['<name>.sandbox/index.html', '<name>.html` '],
|
['<name>.sandbox/index.html', '<name>.html'],
|
||||||
]"
|
]"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -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:
|
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
|
### Unofficial Frameworks
|
||||||
|
|
||||||
|
|||||||
@@ -79,24 +79,41 @@ export default defineConfig({
|
|||||||
|
|
||||||
## ESLint
|
## 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
|
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:
|
||||||
// .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:
|
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// wxt.config.ts
|
// wxt.config.ts
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
imports: {
|
imports: {
|
||||||
eslintrc: {
|
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'],
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|||||||
@@ -1,28 +1,59 @@
|
|||||||
# Frontend Frameworks
|
# Frontend Frameworks
|
||||||
|
|
||||||
WXT supports all frontend frameworks with a Vite plugin:
|
## Built-in Modules
|
||||||
|
|
||||||
- `@vitejs/plugin-vue`
|
WXT has preconfigured modules for 4 frameworks:
|
||||||
- `@vitejs/plugin-react`
|
|
||||||
- `@vitejs/plugin-react-swc`
|
|
||||||
- And more!
|
|
||||||
|
|
||||||
Just add the vite plugin to your config and you're good to go! Use the framework in HTML pages or content scripts, it will just work 👍
|
- [`@wxt-dev/module-react`](https://github.com/wxt-dev/wxt/tree/main/packages/module-react)
|
||||||
|
- [`@wxt-dev/module-vue`](https://github.com/wxt-dev/wxt/tree/main/packages/module-vue)
|
||||||
|
- [`@wxt-dev/module-svelte`](https://github.com/wxt-dev/wxt/tree/main/packages/module-svelte)
|
||||||
|
- [`@wxt-dev/module-solid`](https://github.com/wxt-dev/wxt/tree/main/packages/module-solid)
|
||||||
|
|
||||||
|
Install the module for your framework, then add it to your config:
|
||||||
|
|
||||||
:::code-group
|
:::code-group
|
||||||
|
|
||||||
```ts [Vue]
|
```ts [React]
|
||||||
import { defineConfig } from 'wxt';
|
import { defineConfig } from 'wxt';
|
||||||
import vue from '@vitejs/plugin-vue';
|
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
vite: () => ({
|
modules: ['@wxt-dev/module-react'],
|
||||||
plugins: [vue()],
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
```ts [React]
|
```ts [Vue]
|
||||||
|
import { defineConfig } from 'wxt';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
modules: ['@wxt-dev/module-vue'],
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts [Svelte]
|
||||||
|
import { defineConfig } from 'wxt';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
modules: ['@wxt-dev/module-svelte'],
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts [Solid]
|
||||||
|
import { defineConfig } from 'wxt';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
modules: ['@wxt-dev/module-solid'],
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Adding Vite Plugins
|
||||||
|
|
||||||
|
If your framework doesn't have an official WXT module, no worries! WXT supports any framework with a Vite plugin.
|
||||||
|
|
||||||
|
Just add the Vite plugin to your config and you're good to go! Use the framework in HTML pages or content scripts, it will just work 👍
|
||||||
|
|
||||||
|
```ts
|
||||||
import { defineConfig } from 'wxt';
|
import { defineConfig } from 'wxt';
|
||||||
import react from '@vitejs/plugin-react';
|
import react from '@vitejs/plugin-react';
|
||||||
|
|
||||||
@@ -33,18 +64,7 @@ export default defineConfig({
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
```ts [Svelte]
|
The WXT modules just simplify the configuration and add auto-imports. They're not much different than the above.
|
||||||
import { defineConfig } from 'wxt';
|
|
||||||
import { svelte } from '@sveltejs/vite-plugin-svelte';
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
vite: () => ({
|
|
||||||
plugins: [svelte()],
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
:::
|
|
||||||
|
|
||||||
## Multiple Apps
|
## Multiple Apps
|
||||||
|
|
||||||
@@ -84,9 +104,9 @@ Lots of frameworks come with routers for building a multi-page app using the URL
|
|||||||
|
|
||||||
Instead, you need to configure the router to run in "hash" mode, where the routing information is apart of the URL's hash, not the path (ie: `popup.html#/` and `popup.html#/account/settings`)
|
Instead, you need to configure the router to run in "hash" mode, where the routing information is apart of the URL's hash, not the path (ie: `popup.html#/` and `popup.html#/account/settings`)
|
||||||
|
|
||||||
Refer to your router's docs for information about "hash" mode and how to enable it. Here's a non-extensive list of a few popular routers:
|
Refer to your router's docs for information about hash mode and how to enable it. Here's a non-extensive list of a few popular routers:
|
||||||
|
|
||||||
- [React](https://reactrouter.com/en/main/routers/create-hash-router)
|
- [`react-router`](https://reactrouter.com/en/main/routers/create-hash-router)
|
||||||
- [Vue](https://router.vuejs.org/guide/essentials/history-mode.html#Hash-Mode)
|
- [`vue-router`](https://router.vuejs.org/guide/essentials/history-mode.html#Hash-Mode)
|
||||||
- [Svelte](https://www.npmjs.com/package/svelte-spa-router#hash-based-routing)
|
- [`svelte-spa-router`](https://www.npmjs.com/package/svelte-spa-router#hash-based-routing)
|
||||||
- [Solid](https://github.com/solidjs/solid-router?tab=readme-ov-file#hash-mode-router)
|
- [`solid-router`](https://github.com/solidjs/solid-router?tab=readme-ov-file#hash-mode-router)
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ Here's an example `wxt.config.ts` file:
|
|||||||
import { defineConfig } from 'wxt';
|
import { defineConfig } from 'wxt';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
mainfest: {
|
manifest: {
|
||||||
action: {
|
action: {
|
||||||
default_title: 'Some Title',
|
default_title: 'Some Title',
|
||||||
},
|
},
|
||||||
@@ -176,7 +176,8 @@ export default defineConfig({
|
|||||||
|
|
||||||
See the official localization examples for more details:
|
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
|
## Actions
|
||||||
|
|
||||||
|
|||||||
+6
-6
@@ -15,17 +15,17 @@
|
|||||||
"docs:gen": "typedoc --options docs/typedoc.json",
|
"docs:gen": "typedoc --options docs/typedoc.json",
|
||||||
"docs:dev": "pnpm -s docs:gen && vitepress dev docs",
|
"docs:dev": "pnpm -s docs:gen && vitepress dev docs",
|
||||||
"docs:build": "pnpm -s docs:gen && vitepress build docs",
|
"docs:build": "pnpm -s docs:gen && vitepress build docs",
|
||||||
"docs:preview": "pnpm -s docs:gen && vitepress preview docs",
|
"docs:preview": "pnpm -s docs:gen && vitepress preview docs"
|
||||||
"build-deps": "pnpm tsx scripts/build-deps.ts"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@aklinker1/buildc": "^1.0.10",
|
||||||
"@aklinker1/check": "^1.3.1",
|
"@aklinker1/check": "^1.3.1",
|
||||||
"@types/fs-extra": "^11.0.4",
|
"@types/fs-extra": "^11.0.4",
|
||||||
"@vitest/coverage-v8": "^1.6.0",
|
"@vitest/coverage-v8": "^1.6.0",
|
||||||
"changelogen": "^0.5.5",
|
"changelogen": "^0.5.5",
|
||||||
"consola": "^3.2.3",
|
"consola": "^3.2.3",
|
||||||
"dependency-graph": "^1.0.0",
|
"dependency-graph": "^1.0.0",
|
||||||
"execa": "^9.2.0",
|
"execa": "^9.3.0",
|
||||||
"fast-glob": "^3.3.2",
|
"fast-glob": "^3.3.2",
|
||||||
"fs-extra": "^11.2.0",
|
"fs-extra": "^11.2.0",
|
||||||
"hasha": "^6.0.0",
|
"hasha": "^6.0.0",
|
||||||
@@ -33,16 +33,16 @@
|
|||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"prettier": "^3.3.2",
|
"prettier": "^3.3.2",
|
||||||
"simple-git-hooks": "^2.11.1",
|
"simple-git-hooks": "^2.11.1",
|
||||||
"tsx": "^4.15.4",
|
"tsx": "4.15.7",
|
||||||
"typedoc": "^0.25.4",
|
"typedoc": "^0.25.4",
|
||||||
"typedoc-plugin-markdown": "4.0.0-next.23",
|
"typedoc-plugin-markdown": "4.0.0-next.23",
|
||||||
"typedoc-vitepress-theme": "1.0.0-next.3",
|
"typedoc-vitepress-theme": "1.0.0-next.3",
|
||||||
"typescript": "^5.4.5",
|
"typescript": "^5.5.2",
|
||||||
"vitepress": "^1.2.3",
|
"vitepress": "^1.2.3",
|
||||||
"vitest": "^1.6.0",
|
"vitest": "^1.6.0",
|
||||||
"vitest-mock-extended": "^1.3.1",
|
"vitest-mock-extended": "^1.3.1",
|
||||||
"vitest-plugin-random-seed": "^1.1.0",
|
"vitest-plugin-random-seed": "^1.1.0",
|
||||||
"vue": "^3.4.27",
|
"vue": "^3.4.31",
|
||||||
"wxt": "workspace:*",
|
"wxt": "workspace:*",
|
||||||
"yaml": "^2.4.5"
|
"yaml": "^2.4.5"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## v1.1.0
|
||||||
|
|
||||||
|
[compare changes](https://github.com/wxt-dev/wxt/compare/module-react-v1.0.0...module-react-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))
|
||||||
|
- Use `prepare` instead of `postinstall` for local dev setup ([#788](https://github.com/wxt-dev/wxt/pull/788))
|
||||||
|
|
||||||
|
### 🏡 Chore
|
||||||
|
|
||||||
|
- Add changelog ([21e8ca0](https://github.com/wxt-dev/wxt/commit/21e8ca0))
|
||||||
|
- 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.0
|
||||||
|
|
||||||
|
Initial release 🎉
|
||||||
@@ -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,
|
||||||
|
});
|
||||||
@@ -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');
|
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}',
|
||||||
|
];
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wxt-dev/module-react",
|
"name": "@wxt-dev/module-react",
|
||||||
"version": "1.0.0",
|
"version": "1.1.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.cjs",
|
"main": "./dist/index.cjs",
|
||||||
"module": "./dist/index.mjs",
|
"module": "./dist/index.mjs",
|
||||||
@@ -21,20 +21,25 @@
|
|||||||
"dist"
|
"dist"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "pnpm -s build-deps && unbuild",
|
"dev": "wxt",
|
||||||
"check": "pnpm -s build-deps && check",
|
"check": "buildc --deps-only -- check",
|
||||||
"build-deps": "pnpm -ws build-deps @wxt-dev/module-react"
|
"build": "buildc -- unbuild",
|
||||||
|
"prepare": "buildc --deps-only -- wxt prepare"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"wxt": ">=0.18.5"
|
"wxt": ">=0.18.6"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vitejs/plugin-react": "^4.3.1"
|
"@vitejs/plugin-react": "^4.3.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@aklinker1/check": "^1.3.1",
|
"@aklinker1/check": "^1.3.1",
|
||||||
|
"@types/react": "^18.3.3",
|
||||||
|
"@types/react-dom": "^18.3.0",
|
||||||
"publint": "^0.2.8",
|
"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",
|
"unbuild": "^2.0.0",
|
||||||
"wxt": "workspace:*"
|
"wxt": "workspace:*"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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/**"]
|
"exclude": ["node_modules/**", "dist/**"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## v1.1.1
|
||||||
|
|
||||||
|
[compare changes](https://github.com/wxt-dev/wxt/compare/module-solid-v1.1.0...module-solid-v1.1.1)
|
||||||
|
|
||||||
|
### 🩹 Fixes
|
||||||
|
|
||||||
|
- Use `prepare` instead of `postinstall` for local dev setup ([#788](https://github.com/wxt-dev/wxt/pull/788))
|
||||||
|
|
||||||
|
## 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)
|
||||||
|
|
||||||
|
### 🩹 Fixes
|
||||||
|
|
||||||
|
- Add `target: esnext` by default ([#733](https://github.com/wxt-dev/wxt/pull/733))
|
||||||
|
|
||||||
|
### 🏡 Chore
|
||||||
|
|
||||||
|
- Add changelog ([21e8ca0](https://github.com/wxt-dev/wxt/commit/21e8ca0))
|
||||||
|
|
||||||
|
## v1.0.0
|
||||||
|
|
||||||
|
Initial release 🎉
|
||||||
@@ -4,7 +4,7 @@ Enables the use of [SolidJS](https://www.solidjs.com/) in your web extension, in
|
|||||||
|
|
||||||
This plugin makes a few changes:
|
This plugin makes a few changes:
|
||||||
|
|
||||||
1. Adds `vite-plugin-solid` to vite
|
1. Adds `vite-plugin-solid` to and sets `build.target: esnext` in the vite config
|
||||||
2. Adds the [`solid-js` preset](https://github.com/unjs/unimport/blob/main/src/presets/solid.ts) to auto-imports
|
2. Adds the [`solid-js` preset](https://github.com/unjs/unimport/blob/main/src/presets/solid.ts) to auto-imports
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|||||||
@@ -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,
|
||||||
|
});
|
||||||
@@ -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);
|
||||||
@@ -10,9 +10,23 @@ export default defineWxtModule<SolidModuleOptions>({
|
|||||||
|
|
||||||
addViteConfig(wxt, () => ({
|
addViteConfig(wxt, () => ({
|
||||||
plugins: [solid(vite)],
|
plugins: [solid(vite)],
|
||||||
|
build: {
|
||||||
|
target: 'esnext',
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
addImportPreset(wxt, 'solid-js');
|
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}',
|
||||||
|
];
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wxt-dev/module-solid",
|
"name": "@wxt-dev/module-solid",
|
||||||
"version": "1.0.0",
|
"version": "1.1.1",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.cjs",
|
"main": "./dist/index.cjs",
|
||||||
"module": "./dist/index.mjs",
|
"module": "./dist/index.mjs",
|
||||||
@@ -21,12 +21,13 @@
|
|||||||
"dist"
|
"dist"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "pnpm -s build-deps && unbuild",
|
"dev": "wxt",
|
||||||
"check": "pnpm -s build-deps && check",
|
"check": "buildc --deps-only -- check",
|
||||||
"build-deps": "pnpm -ws build-deps @wxt-dev/module-solid"
|
"build": "buildc -- unbuild",
|
||||||
|
"prepare": "buildc --deps-only -- wxt prepare"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"wxt": ">=0.18.5"
|
"wxt": ">=0.18.6"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"vite-plugin-solid": "^2.10.2"
|
"vite-plugin-solid": "^2.10.2"
|
||||||
@@ -34,7 +35,8 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@aklinker1/check": "^1.3.1",
|
"@aklinker1/check": "^1.3.1",
|
||||||
"publint": "^0.2.8",
|
"publint": "^0.2.8",
|
||||||
"typescript": "^5.4.5",
|
"solid-js": "^1.8.17",
|
||||||
|
"typescript": "^5.5.2",
|
||||||
"unbuild": "^2.0.0",
|
"unbuild": "^2.0.0",
|
||||||
"wxt": "workspace:*"
|
"wxt": "workspace:*"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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/**"]
|
"exclude": ["node_modules/**", "dist/**"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## v1.0.0
|
||||||
|
|
||||||
|
Initial release 🎉
|
||||||
@@ -21,12 +21,11 @@
|
|||||||
"dist"
|
"dist"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "pnpm -s build-deps && unbuild",
|
"build": "buildc -- unbuild",
|
||||||
"check": "pnpm -s build-deps && check",
|
"check": "buildc --deps-only -- check"
|
||||||
"build-deps": "pnpm -ws build-deps @wxt-dev/module-svelte"
|
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"wxt": ">=0.18.5"
|
"wxt": ">=0.18.6"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sveltejs/vite-plugin-svelte": "^3.1.1"
|
"@sveltejs/vite-plugin-svelte": "^3.1.1"
|
||||||
@@ -34,7 +33,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@aklinker1/check": "^1.3.1",
|
"@aklinker1/check": "^1.3.1",
|
||||||
"publint": "^0.2.8",
|
"publint": "^0.2.8",
|
||||||
"typescript": "^5.4.5",
|
"typescript": "^5.5.2",
|
||||||
"unbuild": "^2.0.0",
|
"unbuild": "^2.0.0",
|
||||||
"wxt": "workspace:*"
|
"wxt": "workspace:*"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## v1.0.0
|
||||||
|
|
||||||
|
Initial release 🎉
|
||||||
@@ -21,12 +21,11 @@
|
|||||||
"dist"
|
"dist"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "pnpm -s build-deps && unbuild",
|
"build": "buildc -- unbuild",
|
||||||
"check": "pnpm -s build-deps && check",
|
"check": "buildc --deps-only -- check"
|
||||||
"build-deps": "pnpm -ws build-deps @wxt-dev/module-vue"
|
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"wxt": ">=0.18.5"
|
"wxt": ">=0.18.6"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vitejs/plugin-vue": "^5.0.5"
|
"@vitejs/plugin-vue": "^5.0.5"
|
||||||
@@ -34,7 +33,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@aklinker1/check": "^1.3.1",
|
"@aklinker1/check": "^1.3.1",
|
||||||
"publint": "^0.2.8",
|
"publint": "^0.2.8",
|
||||||
"typescript": "^5.4.5",
|
"typescript": "^5.5.2",
|
||||||
"unbuild": "^2.0.0",
|
"unbuild": "^2.0.0",
|
||||||
"wxt": "workspace:*"
|
"wxt": "workspace:*"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import autoImports from './.wxt/eslintrc-auto-import.js';
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
...autoImports.globals,
|
||||||
|
},
|
||||||
|
sourceType: 'module',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
@@ -4,18 +4,17 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "pnpm -s build-deps && wxt",
|
"dev": "buildc --deps-only -- wxt",
|
||||||
"build": "pnpm -s build-deps && wxt build",
|
"build": "buildc -- wxt build",
|
||||||
"build:all": "pnpm -s build-deps && run-s -s 'build:all:*'",
|
"build:all": "buildc --deps-only -- run-s -s 'build:all:*'",
|
||||||
"build:all:chrome-mv3": "wxt build",
|
"build:all:chrome-mv3": "wxt build",
|
||||||
"build:all:chrome-mv2": "wxt build --mv2",
|
"build:all:chrome-mv2": "wxt build --mv2",
|
||||||
"build:all:firefox-mv3": "wxt build -b firefox --mv3",
|
"build:all:firefox-mv3": "wxt build -b firefox --mv3",
|
||||||
"build:all:firefox-mv2": "wxt build -b firefox",
|
"build:all:firefox-mv2": "wxt build -b firefox",
|
||||||
"test": "pnpm -s build-deps && vitest",
|
"test": "buildc --deps-only -- vitest",
|
||||||
"zip": "pnpm -s build-deps && wxt zip",
|
"zip": "buildc --deps-only -- wxt zip",
|
||||||
"check": "pnpm -s build-deps && check",
|
"check": "buildc --deps-only -- check",
|
||||||
"postinstall": "pnpm -s build-deps && wxt prepare",
|
"postinstall": "buildc --deps-only -- wxt prepare"
|
||||||
"build-deps": "pnpm -sw build-deps wxt-demo"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
@@ -24,8 +23,11 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react": "^18.3.3",
|
"@types/react": "^18.3.3",
|
||||||
"@types/react-dom": "^18.3.0",
|
"@types/react-dom": "^18.3.0",
|
||||||
"sass": "^1.77.5",
|
"sass": "^1.77.6",
|
||||||
"typescript": "^5.4.5",
|
"typescript": "^5.5.2",
|
||||||
"wxt": "workspace:*"
|
"wxt": "workspace:*"
|
||||||
|
},
|
||||||
|
"buildc": {
|
||||||
|
"outDir": ".output/chrome-mv3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export default defineContentScript({
|
|||||||
anchor: 'form[role=search]',
|
anchor: 'form[role=search]',
|
||||||
onMount: (container) => {
|
onMount: (container) => {
|
||||||
const app = document.createElement('div');
|
const app = document.createElement('div');
|
||||||
app.textContent = 'Custom content script UI';
|
app.textContent = browser.i18n.getMessage('prompt_for_name');
|
||||||
container.append(app);
|
container.append(app);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -19,7 +19,10 @@ export default defineConfig({
|
|||||||
open: true,
|
open: true,
|
||||||
},
|
},
|
||||||
experimental: {
|
experimental: {
|
||||||
viteRuntime: true,
|
entrypointImporter: 'vite-node',
|
||||||
|
},
|
||||||
|
runner: {
|
||||||
|
startUrls: ['https://duckduckgo.com'],
|
||||||
},
|
},
|
||||||
example: {
|
example: {
|
||||||
a: 'a',
|
a: 'a',
|
||||||
|
|||||||
@@ -1,5 +1,65 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## v0.18.10
|
||||||
|
|
||||||
|
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.18.9...wxt-v0.18.10)
|
||||||
|
|
||||||
|
### 🚀 Enhancements
|
||||||
|
|
||||||
|
- Add `prepare:types` hook to extend `.wxt/` directory generation ([#767](https://github.com/wxt-dev/wxt/pull/767))
|
||||||
|
- **modules:** Allow adding generated public files ([#769](https://github.com/wxt-dev/wxt/pull/769))
|
||||||
|
|
||||||
|
### 🩹 Fixes
|
||||||
|
|
||||||
|
- Await `prepare:types` hook ([b29d49c](https://github.com/wxt-dev/wxt/commit/b29d49c))
|
||||||
|
|
||||||
|
### 🏡 Chore
|
||||||
|
|
||||||
|
- Refactor package manager test fixtures ([39f6c29](https://github.com/wxt-dev/wxt/commit/39f6c29))
|
||||||
|
- Consolidate `unimport` code into a built-in module ([#771](https://github.com/wxt-dev/wxt/pull/771))
|
||||||
|
|
||||||
|
## v0.18.9
|
||||||
|
|
||||||
|
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.18.8...wxt-v0.18.9)
|
||||||
|
|
||||||
|
### 🚀 Enhancements
|
||||||
|
|
||||||
|
- **experimental:** Replace `viteRuntime` option with `entrypointImporter` option, and implement `vite-node` importer ([#761](https://github.com/wxt-dev/wxt/pull/761))
|
||||||
|
|
||||||
|
## v0.18.8
|
||||||
|
|
||||||
|
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.18.7...wxt-v0.18.8)
|
||||||
|
|
||||||
|
### 🚀 Enhancements
|
||||||
|
|
||||||
|
- **dev:** Reload extension when public files change ([#752](https://github.com/wxt-dev/wxt/pull/752))
|
||||||
|
|
||||||
|
### 🩹 Fixes
|
||||||
|
|
||||||
|
- Don't load plugins twice in HTML pages ([#746](https://github.com/wxt-dev/wxt/pull/746))
|
||||||
|
- Ignore .wxt file changes in dev ([#755](https://github.com/wxt-dev/wxt/pull/755))
|
||||||
|
- **modules:** `addViteConfig` ignored user vite config ([#760](https://github.com/wxt-dev/wxt/pull/760))
|
||||||
|
|
||||||
|
### 🏡 Chore
|
||||||
|
|
||||||
|
- Refactor client web socket util ([#753](https://github.com/wxt-dev/wxt/pull/753))
|
||||||
|
- Add E2E test for `addImportPreset` ([9fc6408](https://github.com/wxt-dev/wxt/commit/9fc6408))
|
||||||
|
|
||||||
|
## v0.18.7
|
||||||
|
|
||||||
|
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.18.6...wxt-v0.18.7)
|
||||||
|
|
||||||
|
### 🩹 Fixes
|
||||||
|
|
||||||
|
- **dev:** Fix CSP error loading HTML plugins ([#723](https://github.com/wxt-dev/wxt/pull/723))
|
||||||
|
- Generalize react-refresh preamble logic to virtualize all inline scripts ([#728](https://github.com/wxt-dev/wxt/pull/728))
|
||||||
|
- **zip:** Revert dotfile changes from #674 ([#742](https://github.com/wxt-dev/wxt/pull/742), [#674](https://github.com/wxt-dev/wxt/issues/674))
|
||||||
|
|
||||||
|
### 🏡 Chore
|
||||||
|
|
||||||
|
- **deps:** Upgrade and sync all dependencies ([#725](https://github.com/wxt-dev/wxt/pull/725))
|
||||||
|
- Extract build cache script to NPM package ([#737](https://github.com/wxt-dev/wxt/pull/737))
|
||||||
|
|
||||||
## v0.18.6
|
## v0.18.6
|
||||||
|
|
||||||
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.18.5...wxt-v0.18.6)
|
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.18.5...wxt-v0.18.6)
|
||||||
|
|||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"
|
||||||
|
`;
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { describe, it, expect } from 'vitest';
|
import { describe, it, expect } from 'vitest';
|
||||||
import { TestProject } from '../utils';
|
import { TestProject } from '../utils';
|
||||||
|
import { execaCommand } from 'execa';
|
||||||
|
|
||||||
describe('Auto Imports', () => {
|
describe('Auto Imports', () => {
|
||||||
describe('imports: { ... }', () => {
|
describe('imports: { ... }', () => {
|
||||||
@@ -47,10 +48,10 @@ describe('Auto Imports', () => {
|
|||||||
----------------------------------------
|
----------------------------------------
|
||||||
// Generated by wxt
|
// Generated by wxt
|
||||||
/// <reference types="wxt/vite-builder-env" />
|
/// <reference types="wxt/vite-builder-env" />
|
||||||
/// <reference types="./types/imports.d.ts" />
|
|
||||||
/// <reference types="./types/paths.d.ts" />
|
/// <reference types="./types/paths.d.ts" />
|
||||||
/// <reference types="./types/i18n.d.ts" />
|
/// <reference types="./types/i18n.d.ts" />
|
||||||
/// <reference types="./types/globals.d.ts" />
|
/// <reference types="./types/globals.d.ts" />
|
||||||
|
/// <reference types="./types/imports.d.ts" />
|
||||||
"
|
"
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
@@ -96,7 +97,7 @@ describe('Auto Imports', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('eslintrc', () => {
|
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();
|
const project = new TestProject();
|
||||||
project.addFile('entrypoints/popup.html', `<html></html>`);
|
project.addFile('entrypoints/popup.html', `<html></html>`);
|
||||||
|
|
||||||
@@ -108,30 +109,43 @@ describe('Auto Imports', () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(await project.serializeFile('.wxt/eslintrc-auto-import.json'))
|
expect(
|
||||||
.toMatchInlineSnapshot(`
|
await project.serializeFile('.wxt/eslintrc-auto-import.json'),
|
||||||
".wxt/eslintrc-auto-import.json
|
).toMatchSnapshot();
|
||||||
----------------------------------------
|
});
|
||||||
{
|
|
||||||
"globals": {
|
it('"enabled: 8" should output a JSON config file compatible with ESlint 8', async () => {
|
||||||
"ContentScriptContext": true,
|
const project = new TestProject();
|
||||||
"InvalidMatchPattern": true,
|
project.addFile('entrypoints/popup.html', `<html></html>`);
|
||||||
"MatchPattern": true,
|
|
||||||
"browser": true,
|
await project.prepare({
|
||||||
"createIframeUi": true,
|
imports: {
|
||||||
"createIntegratedUi": true,
|
eslintrc: {
|
||||||
"createShadowRootUi": true,
|
enabled: 8,
|
||||||
"defineBackground": true,
|
},
|
||||||
"defineConfig": true,
|
},
|
||||||
"defineContentScript": true,
|
});
|
||||||
"defineUnlistedScript": true,
|
|
||||||
"defineWxtPlugin": true,
|
expect(
|
||||||
"fakeBrowser": true,
|
await project.serializeFile('.wxt/eslintrc-auto-import.json'),
|
||||||
"storage": true
|
).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 () => {
|
it('should allow customizing the output', async () => {
|
||||||
@@ -148,30 +162,122 @@ describe('Auto Imports', () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(await project.serializeFile('example.json'))
|
expect(await project.serializeFile('example.json')).toMatchSnapshot();
|
||||||
.toMatchInlineSnapshot(`
|
});
|
||||||
"example.json
|
|
||||||
----------------------------------------
|
describe('Actual linting results', () => {
|
||||||
{
|
async function runEslint(
|
||||||
"globals": {
|
project: TestProject,
|
||||||
"ContentScriptContext": "readonly",
|
version: boolean | 'auto' | 8 | 9,
|
||||||
"InvalidMatchPattern": "readonly",
|
) {
|
||||||
"MatchPattern": "readonly",
|
project.addFile(
|
||||||
"browser": "readonly",
|
'entrypoints/background.js',
|
||||||
"createIframeUi": "readonly",
|
`export default defineBackground(() => {})`,
|
||||||
"createIntegratedUi": "readonly",
|
);
|
||||||
"createShadowRootUi": "readonly",
|
await project.prepare({
|
||||||
"defineBackground": "readonly",
|
imports: { eslintrc: { enabled: version } },
|
||||||
"defineConfig": "readonly",
|
});
|
||||||
"defineContentScript": "readonly",
|
return await execaCommand('pnpm eslint entrypoints/background.js', {
|
||||||
"defineUnlistedScript": "readonly",
|
cwd: project.root,
|
||||||
"defineWxtPlugin": "readonly",
|
});
|
||||||
"fakeBrowser": "readonly",
|
}
|
||||||
"storage": "readonly"
|
|
||||||
}
|
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();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { WxtHooks } from '~/types';
|
|||||||
|
|
||||||
const hooks: WxtHooks = {
|
const hooks: WxtHooks = {
|
||||||
ready: vi.fn(),
|
ready: vi.fn(),
|
||||||
|
'prepare:types': vi.fn(),
|
||||||
'build:before': vi.fn(),
|
'build:before': vi.fn(),
|
||||||
'build:done': vi.fn(),
|
'build:done': vi.fn(),
|
||||||
'build:manifestGenerated': vi.fn(),
|
'build:manifestGenerated': vi.fn(),
|
||||||
@@ -41,6 +42,7 @@ describe('Hooks', () => {
|
|||||||
|
|
||||||
expectHooksToBeCalled({
|
expectHooksToBeCalled({
|
||||||
ready: true,
|
ready: true,
|
||||||
|
'prepare:types': true,
|
||||||
'build:before': false,
|
'build:before': false,
|
||||||
'build:done': false,
|
'build:done': false,
|
||||||
'build:publicAssets': false,
|
'build:publicAssets': false,
|
||||||
@@ -60,6 +62,7 @@ describe('Hooks', () => {
|
|||||||
|
|
||||||
expectHooksToBeCalled({
|
expectHooksToBeCalled({
|
||||||
ready: true,
|
ready: true,
|
||||||
|
'prepare:types': true,
|
||||||
'build:before': true,
|
'build:before': true,
|
||||||
'build:done': true,
|
'build:done': true,
|
||||||
'build:publicAssets': true,
|
'build:publicAssets': true,
|
||||||
@@ -79,6 +82,7 @@ describe('Hooks', () => {
|
|||||||
|
|
||||||
expectHooksToBeCalled({
|
expectHooksToBeCalled({
|
||||||
ready: true,
|
ready: true,
|
||||||
|
'prepare:types': true,
|
||||||
'build:before': true,
|
'build:before': true,
|
||||||
'build:done': true,
|
'build:done': true,
|
||||||
'build:publicAssets': true,
|
'build:publicAssets': true,
|
||||||
@@ -104,6 +108,7 @@ describe('Hooks', () => {
|
|||||||
|
|
||||||
expectHooksToBeCalled({
|
expectHooksToBeCalled({
|
||||||
ready: true,
|
ready: true,
|
||||||
|
'prepare:types': true,
|
||||||
'build:before': true,
|
'build:before': true,
|
||||||
'build:done': true,
|
'build:done': true,
|
||||||
'build:publicAssets': true,
|
'build:publicAssets': true,
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { describe, it, expect } from 'vitest';
|
import { describe, it, expect } from 'vitest';
|
||||||
import { TestProject } from '../utils';
|
import { TestProject } from '../utils';
|
||||||
|
|
||||||
describe.each([true, false])(
|
describe.each(['jiti', 'vite-runtime', 'vite-node'] as const)(
|
||||||
'Manifest Content (Vite runtime? %s)',
|
'Manifest Content (Vite runtime? %s)',
|
||||||
(viteRuntime) => {
|
(entrypointImporter) => {
|
||||||
it.each([
|
it.each([
|
||||||
{ browser: undefined, outDir: 'chrome-mv3', expected: undefined },
|
{ browser: undefined, outDir: 'chrome-mv3', expected: undefined },
|
||||||
{ browser: 'chrome', outDir: 'chrome-mv3', expected: undefined },
|
{ browser: 'chrome', outDir: 'chrome-mv3', expected: undefined },
|
||||||
@@ -24,7 +24,7 @@ describe.each([true, false])(
|
|||||||
main: () => {},
|
main: () => {},
|
||||||
})`,
|
})`,
|
||||||
);
|
);
|
||||||
await project.build({ browser, experimental: { viteRuntime } });
|
await project.build({ browser, experimental: { entrypointImporter } });
|
||||||
|
|
||||||
const safariManifest = await project.getOutputManifest(
|
const safariManifest = await project.getOutputManifest(
|
||||||
`.output/${outDir}/manifest.json`,
|
`.output/${outDir}/manifest.json`,
|
||||||
|
|||||||
@@ -101,7 +101,13 @@ describe('Module Helpers', () => {
|
|||||||
|
|
||||||
export default defineWxtModule((wxt) => {
|
export default defineWxtModule((wxt) => {
|
||||||
addPublicAssets(wxt, "${normalizePath(dir)}")
|
addPublicAssets(wxt, "${normalizePath(dir)}")
|
||||||
})
|
wxt.hooks.hook("build:publicAssets", (_, assets) => {
|
||||||
|
assets.push({
|
||||||
|
relativeDest: "example/generated.txt",
|
||||||
|
contents: "",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -114,6 +120,9 @@ describe('Module Helpers', () => {
|
|||||||
await expect(
|
await expect(
|
||||||
project.fileExists('.output/chrome-mv3/module.txt'),
|
project.fileExists('.output/chrome-mv3/module.txt'),
|
||||||
).resolves.toBe(true);
|
).resolves.toBe(true);
|
||||||
|
await expect(
|
||||||
|
project.fileExists('.output/chrome-mv3/example/generated.txt'),
|
||||||
|
).resolves.toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not overwrite the user's public files", async () => {
|
it("should not overwrite the user's public files", async () => {
|
||||||
@@ -287,5 +296,29 @@ describe('Module Helpers', () => {
|
|||||||
|
|
||||||
await expect(project.serializeOutput()).resolves.toContain(expectedText);
|
await expect(project.serializeOutput()).resolves.toContain(expectedText);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should add preset', async () => {
|
||||||
|
const project = new TestProject();
|
||||||
|
project.addFile(
|
||||||
|
'entrypoints/background.ts',
|
||||||
|
`export default defineBackground(() => {
|
||||||
|
customImport();
|
||||||
|
});`,
|
||||||
|
);
|
||||||
|
project.addFile(
|
||||||
|
'modules/test.ts',
|
||||||
|
`import { defineWxtModule, addImportPreset } from 'wxt/modules';
|
||||||
|
|
||||||
|
export default defineWxtModule((wxt) => {
|
||||||
|
addImportPreset(wxt, "vue");
|
||||||
|
})`,
|
||||||
|
);
|
||||||
|
|
||||||
|
await project.build();
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
project.serializeFile('.wxt/types/imports.d.ts'),
|
||||||
|
).resolves.toContain("const ref: typeof import('vue')['ref']");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -233,10 +233,10 @@ describe('TypeScript Project', () => {
|
|||||||
----------------------------------------
|
----------------------------------------
|
||||||
// Generated by wxt
|
// Generated by wxt
|
||||||
/// <reference types="wxt/vite-builder-env" />
|
/// <reference types="wxt/vite-builder-env" />
|
||||||
/// <reference types="./types/imports.d.ts" />
|
|
||||||
/// <reference types="./types/paths.d.ts" />
|
/// <reference types="./types/paths.d.ts" />
|
||||||
/// <reference types="./types/i18n.d.ts" />
|
/// <reference types="./types/i18n.d.ts" />
|
||||||
/// <reference types="./types/globals.d.ts" />
|
/// <reference types="./types/globals.d.ts" />
|
||||||
|
/// <reference types="./types/imports.d.ts" />
|
||||||
"
|
"
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -97,7 +97,8 @@ describe('Zipping', () => {
|
|||||||
expect(await project.fileExists(sourcesZip)).toBe(true);
|
expect(await project.fileExists(sourcesZip)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not zip hidden files into sources by default', async () => {
|
// TODO: Fix #738 and re-enable
|
||||||
|
it.skip('should not zip hidden files into sources by default', async () => {
|
||||||
const project = new TestProject({
|
const project = new TestProject({
|
||||||
name: 'test',
|
name: 'test',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
@@ -117,7 +118,8 @@ describe('Zipping', () => {
|
|||||||
expect(await project.fileExists(unzipDir, '.env')).toBe(false);
|
expect(await project.fileExists(unzipDir, '.env')).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow zipping hidden files into sources when explicitly listed', async () => {
|
// TODO: Fix #738 and re-enable
|
||||||
|
it.skip('should allow zipping hidden files into sources when explicitly listed', async () => {
|
||||||
const project = new TestProject({
|
const project = new TestProject({
|
||||||
name: 'test',
|
name: 'test',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
|
|||||||
+14
-13
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "wxt",
|
"name": "wxt",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "0.18.6",
|
"version": "0.18.10",
|
||||||
"description": "Next gen framework for developing web extensions",
|
"description": "Next gen framework for developing web extensions",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -92,12 +92,12 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"wxt": "tsx src/cli/index.ts",
|
"wxt": "tsx src/cli/index.ts",
|
||||||
"build": "tsx scripts/build.ts",
|
"build": "buildc -- tsx scripts/build.ts",
|
||||||
"check": "run-s -c check:*",
|
"check": "buildc --deps-only -- run-s -c check:*",
|
||||||
"check:default": "check",
|
"check:default": "check",
|
||||||
"check:tsc-virtual": "tsc --noEmit -p src/virtual",
|
"check:tsc-virtual": "tsc --noEmit -p src/virtual",
|
||||||
"test": "vitest",
|
"test": "buildc --deps-only -- vitest",
|
||||||
"test:e2e": "vitest -r e2e",
|
"test:e2e": "buildc --deps-only -- vitest -r e2e",
|
||||||
"sync-releases": "pnpx changelogen@latest gh release"
|
"sync-releases": "pnpx changelogen@latest gh release"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -124,12 +124,12 @@
|
|||||||
"jiti": "^1.21.6",
|
"jiti": "^1.21.6",
|
||||||
"json5": "^2.2.3",
|
"json5": "^2.2.3",
|
||||||
"jszip": "^3.10.1",
|
"jszip": "^3.10.1",
|
||||||
"linkedom": "^0.18.3",
|
"linkedom": "^0.18.4",
|
||||||
"magicast": "^0.3.4",
|
"magicast": "^0.3.4",
|
||||||
"minimatch": "^9.0.4",
|
"minimatch": "^9.0.5",
|
||||||
"natural-compare": "^1.4.0",
|
"natural-compare": "^1.4.0",
|
||||||
"normalize-path": "^3.0.0",
|
"normalize-path": "^3.0.0",
|
||||||
"nypm": "^0.3.8",
|
"nypm": "^0.3.9",
|
||||||
"ohash": "^1.1.3",
|
"ohash": "^1.1.3",
|
||||||
"open": "^10.1.0",
|
"open": "^10.1.0",
|
||||||
"ora": "^7.0.1",
|
"ora": "^7.0.1",
|
||||||
@@ -137,7 +137,8 @@
|
|||||||
"prompts": "^2.4.2",
|
"prompts": "^2.4.2",
|
||||||
"publish-browser-extension": "^2.1.3",
|
"publish-browser-extension": "^2.1.3",
|
||||||
"unimport": "^3.7.2",
|
"unimport": "^3.7.2",
|
||||||
"vite": "^5.3.0",
|
"vite": "^5.3.2",
|
||||||
|
"vite-node": "^1.6.0",
|
||||||
"web-ext-run": "^0.2.0",
|
"web-ext-run": "^0.2.0",
|
||||||
"webextension-polyfill": "^0.10.0"
|
"webextension-polyfill": "^0.10.0"
|
||||||
},
|
},
|
||||||
@@ -147,17 +148,17 @@
|
|||||||
"@types/fs-extra": "^11.0.4",
|
"@types/fs-extra": "^11.0.4",
|
||||||
"@types/lodash.merge": "^4.6.9",
|
"@types/lodash.merge": "^4.6.9",
|
||||||
"@types/natural-compare": "^1.4.3",
|
"@types/natural-compare": "^1.4.3",
|
||||||
"@types/node": "^20.14.2",
|
"@types/node": "^20.14.9",
|
||||||
"@types/normalize-path": "^3.0.2",
|
"@types/normalize-path": "^3.0.2",
|
||||||
"@types/prompts": "^2.4.9",
|
"@types/prompts": "^2.4.9",
|
||||||
"execa": "^9.2.0",
|
"execa": "^9.3.0",
|
||||||
"extract-zip": "^2.0.1",
|
"extract-zip": "^2.0.1",
|
||||||
"happy-dom": "^13.10.1",
|
"happy-dom": "^13.10.1",
|
||||||
"lodash.merge": "^4.6.2",
|
"lodash.merge": "^4.6.2",
|
||||||
"p-map": "^7.0.2",
|
"p-map": "^7.0.2",
|
||||||
"publint": "^0.2.8",
|
"publint": "^0.2.8",
|
||||||
"tsup": "^8.1.0",
|
"tsup": "^8.1.0",
|
||||||
"tsx": "^4.15.4",
|
"tsx": "4.15.7",
|
||||||
"typescript": "^5.4.5"
|
"typescript": "^5.5.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,10 +13,10 @@ describe('Module Utilities', () => {
|
|||||||
const userConfig = {};
|
const userConfig = {};
|
||||||
const moduleConfig = { build: { sourcemap: true } };
|
const moduleConfig = { build: { sourcemap: true } };
|
||||||
|
|
||||||
wxt.config.vite = () => userConfig;
|
wxt.config.vite = () => Promise.resolve(userConfig);
|
||||||
addViteConfig(wxt, () => moduleConfig);
|
addViteConfig(wxt, () => moduleConfig);
|
||||||
await wxt.hooks.callHook('ready', wxt);
|
await wxt.hooks.callHook('ready', wxt);
|
||||||
const actual: any = wxt.config.vite(wxt.config.env);
|
const actual = await wxt.config.vite(wxt.config.env);
|
||||||
|
|
||||||
expect(actual).toEqual(expected);
|
expect(actual).toEqual(expected);
|
||||||
});
|
});
|
||||||
@@ -32,7 +32,7 @@ describe('Module Utilities', () => {
|
|||||||
wxt.config.vite = () => userConfig;
|
wxt.config.vite = () => userConfig;
|
||||||
addViteConfig(wxt, () => moduleConfig);
|
addViteConfig(wxt, () => moduleConfig);
|
||||||
await wxt.hooks.callHook('ready', wxt);
|
await wxt.hooks.callHook('ready', wxt);
|
||||||
const actual: any = wxt.config.vite(wxt.config.env);
|
const actual = await wxt.config.vite(wxt.config.env);
|
||||||
|
|
||||||
expect(actual).toEqual(expected);
|
expect(actual).toEqual(expected);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import { WxtModule } from '~/types';
|
||||||
|
import unimport from './unimport';
|
||||||
|
|
||||||
|
export const builtinModules: WxtModule<any>[] = [unimport];
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
import { addViteConfig, defineWxtModule } from '~/modules';
|
||||||
|
import type {
|
||||||
|
EslintGlobalsPropValue,
|
||||||
|
WxtDirFileEntry,
|
||||||
|
WxtModule,
|
||||||
|
WxtResolvedUnimportOptions,
|
||||||
|
} from '~/types';
|
||||||
|
import { type Unimport, createUnimport } from 'unimport';
|
||||||
|
import { Plugin } from 'vite';
|
||||||
|
import { extname } from 'node:path';
|
||||||
|
|
||||||
|
export default defineWxtModule({
|
||||||
|
name: 'wxt:built-in:unimport',
|
||||||
|
setup(wxt) {
|
||||||
|
const options = wxt.config.imports;
|
||||||
|
if (options === false) return;
|
||||||
|
|
||||||
|
let unimport: Unimport;
|
||||||
|
|
||||||
|
// Add user module imports to config
|
||||||
|
wxt.hooks.hook('ready', () => {
|
||||||
|
const addModuleImports = (module: WxtModule<any>) => {
|
||||||
|
if (!module.imports) return;
|
||||||
|
|
||||||
|
options.imports ??= [];
|
||||||
|
options.imports.push(...module.imports);
|
||||||
|
};
|
||||||
|
|
||||||
|
wxt.config.builtinModules.forEach(addModuleImports);
|
||||||
|
wxt.config.userModules.forEach(addModuleImports);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Generate types
|
||||||
|
wxt.hooks.hook('prepare:types', async (_, entries) => {
|
||||||
|
// Update cache before each rebuild
|
||||||
|
await unimport.init();
|
||||||
|
|
||||||
|
entries.push(await getImportsDeclarationEntry(unimport));
|
||||||
|
|
||||||
|
if (options.eslintrc.enabled === false) return;
|
||||||
|
entries.push(
|
||||||
|
await getEslintConfigEntry(unimport, options.eslintrc.enabled, options),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add vite plugin
|
||||||
|
addViteConfig(wxt, () => ({
|
||||||
|
plugins: [vitePlugin(unimport)],
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export function vitePlugin(unimport: Unimport): Plugin {
|
||||||
|
const ENABLED_EXTENSIONS = new Set([
|
||||||
|
'.js',
|
||||||
|
'.jsx',
|
||||||
|
'.ts',
|
||||||
|
'.tsx',
|
||||||
|
'.vue',
|
||||||
|
'.svelte',
|
||||||
|
]);
|
||||||
|
return {
|
||||||
|
name: 'wxt:unimport',
|
||||||
|
async transform(code, id) {
|
||||||
|
// Don't transform dependencies
|
||||||
|
if (id.includes('node_modules')) return;
|
||||||
|
|
||||||
|
// Don't transform non-js files
|
||||||
|
if (!ENABLED_EXTENSIONS.has(extname(id))) return;
|
||||||
|
|
||||||
|
const injected = await unimport.injectImports(code, id);
|
||||||
|
return {
|
||||||
|
code: injected.code,
|
||||||
|
map: injected.s.generateMap({ hires: 'boundary', source: id }),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getImportsDeclarationEntry(
|
||||||
|
unimport: Unimport,
|
||||||
|
): Promise<WxtDirFileEntry> {
|
||||||
|
// Load project imports into unimport memory so they are output via generateTypeDeclarations
|
||||||
|
await unimport.init();
|
||||||
|
|
||||||
|
return {
|
||||||
|
path: 'types/imports.d.ts',
|
||||||
|
text: [
|
||||||
|
'// Generated by wxt',
|
||||||
|
await unimport.generateTypeDeclarations(),
|
||||||
|
'',
|
||||||
|
].join('\n'),
|
||||||
|
tsReference: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getEslintConfigEntry(
|
||||||
|
unimport: Unimport,
|
||||||
|
version: 8 | 9,
|
||||||
|
options: WxtResolvedUnimportOptions,
|
||||||
|
): Promise<WxtDirFileEntry> {
|
||||||
|
const globals = (await unimport.getImports())
|
||||||
|
.map((i) => i.as ?? i.name)
|
||||||
|
.filter(Boolean)
|
||||||
|
.sort()
|
||||||
|
.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({ 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",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -345,14 +345,32 @@ describe('CLI', () => {
|
|||||||
mockArgv('clean');
|
mockArgv('clean');
|
||||||
await importCli();
|
await importCli();
|
||||||
|
|
||||||
expect(cleanMock).toBeCalledWith(undefined);
|
expect(cleanMock).toBeCalledWith({});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should respect passing a custom root', async () => {
|
it('should respect passing a custom root', async () => {
|
||||||
mockArgv('clean', 'path/to/root');
|
mockArgv('clean', 'path/to/root');
|
||||||
await importCli();
|
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,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -126,9 +126,10 @@ cli
|
|||||||
cli
|
cli
|
||||||
.command('clean [root]', 'clean generated files and caches')
|
.command('clean [root]', 'clean generated files and caches')
|
||||||
.alias('cleanup')
|
.alias('cleanup')
|
||||||
|
.option('-c, --config <file>', 'use specified config file')
|
||||||
.action(
|
.action(
|
||||||
wrapAction(async (root, flags) => {
|
wrapAction(async (root, flags) => {
|
||||||
await clean(root);
|
await clean({ root, configFile: flags.config, debug: flags.debug });
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
import { describe, it, expect } from 'vitest';
|
|
||||||
import { createViteBuilder } from '../index';
|
|
||||||
import { fakeResolvedConfig } from '~/core/utils/testing/fake-objects';
|
|
||||||
import { createHooks } from 'hookable';
|
|
||||||
|
|
||||||
describe('Vite Builder', () => {
|
|
||||||
describe('importEntrypoint', () => {
|
|
||||||
it('should import entrypoints, removing runtime values (like the main function)', async () => {
|
|
||||||
const {
|
|
||||||
default: { main: _, ...expected },
|
|
||||||
} = await import('./fixtures/module');
|
|
||||||
const builder = await createViteBuilder(
|
|
||||||
fakeResolvedConfig({ root: __dirname }),
|
|
||||||
createHooks(),
|
|
||||||
);
|
|
||||||
const actual = await builder.importEntrypoint<{ default: any }>(
|
|
||||||
'./fixtures/module.ts',
|
|
||||||
);
|
|
||||||
expect(actual).toEqual(expected);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -20,6 +20,10 @@ import {
|
|||||||
import { Hookable } from 'hookable';
|
import { Hookable } from 'hookable';
|
||||||
import { toArray } from '~/core/utils/arrays';
|
import { toArray } from '~/core/utils/arrays';
|
||||||
import { safeVarName } from '~/core/utils/strings';
|
import { safeVarName } from '~/core/utils/strings';
|
||||||
|
import { importEntrypointFile } from '~/core/utils/building';
|
||||||
|
import { ViteNodeServer } from 'vite-node/server';
|
||||||
|
import { ViteNodeRunner } from 'vite-node/client';
|
||||||
|
import { installSourcemapsSupport } from 'vite-node/source-map';
|
||||||
|
|
||||||
export async function createViteBuilder(
|
export async function createViteBuilder(
|
||||||
wxtConfig: ResolvedConfig,
|
wxtConfig: ResolvedConfig,
|
||||||
@@ -57,7 +61,6 @@ export async function createViteBuilder(
|
|||||||
config.plugins.push(
|
config.plugins.push(
|
||||||
wxtPlugins.download(wxtConfig),
|
wxtPlugins.download(wxtConfig),
|
||||||
wxtPlugins.devHtmlPrerender(wxtConfig, server),
|
wxtPlugins.devHtmlPrerender(wxtConfig, server),
|
||||||
wxtPlugins.unimport(wxtConfig),
|
|
||||||
wxtPlugins.resolveVirtualModules(wxtConfig),
|
wxtPlugins.resolveVirtualModules(wxtConfig),
|
||||||
wxtPlugins.devServerGlobals(wxtConfig, server),
|
wxtPlugins.devServerGlobals(wxtConfig, server),
|
||||||
wxtPlugins.tsconfigPaths(wxtConfig),
|
wxtPlugins.tsconfigPaths(wxtConfig),
|
||||||
@@ -204,21 +207,68 @@ export async function createViteBuilder(
|
|||||||
return {
|
return {
|
||||||
name: 'Vite',
|
name: 'Vite',
|
||||||
version: vite.version,
|
version: vite.version,
|
||||||
async importEntrypoint(url) {
|
async importEntrypoint(path) {
|
||||||
const baseConfig = await getBaseConfig();
|
switch (wxtConfig.experimental.entrypointImporter) {
|
||||||
const envConfig: vite.InlineConfig = {
|
default:
|
||||||
plugins: [
|
case 'jiti': {
|
||||||
wxtPlugins.webextensionPolyfillMock(wxtConfig),
|
return await importEntrypointFile(path);
|
||||||
wxtPlugins.removeEntrypointMainFunction(wxtConfig, url),
|
}
|
||||||
],
|
case 'vite-runtime': {
|
||||||
};
|
const baseConfig = await getBaseConfig();
|
||||||
const config = vite.mergeConfig(baseConfig, envConfig);
|
const envConfig: vite.InlineConfig = {
|
||||||
const server = await vite.createServer(config);
|
plugins: [
|
||||||
await server.listen();
|
wxtPlugins.webextensionPolyfillMock(wxtConfig),
|
||||||
const runtime = await vite.createViteRuntime(server, { hmr: false });
|
wxtPlugins.removeEntrypointMainFunction(wxtConfig, path),
|
||||||
const module = await runtime.executeUrl(url);
|
],
|
||||||
await server.close();
|
};
|
||||||
return module.default;
|
const config = vite.mergeConfig(baseConfig, envConfig);
|
||||||
|
const server = await vite.createServer(config);
|
||||||
|
await server.listen();
|
||||||
|
const runtime = await vite.createViteRuntime(server, { hmr: false });
|
||||||
|
const module = await runtime.executeUrl(path);
|
||||||
|
await server.close();
|
||||||
|
return module.default;
|
||||||
|
}
|
||||||
|
case 'vite-node': {
|
||||||
|
const baseConfig = await getBaseConfig();
|
||||||
|
// Disable dep optimization, as recommended by vite-node's README
|
||||||
|
baseConfig.optimizeDeps ??= {};
|
||||||
|
baseConfig.optimizeDeps.noDiscovery = true;
|
||||||
|
baseConfig.optimizeDeps.include = [];
|
||||||
|
const envConfig: vite.InlineConfig = {
|
||||||
|
plugins: [
|
||||||
|
wxtPlugins.webextensionPolyfillMock(wxtConfig),
|
||||||
|
wxtPlugins.removeEntrypointMainFunction(wxtConfig, path),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const config = vite.mergeConfig(baseConfig, envConfig);
|
||||||
|
const server = await vite.createServer(config);
|
||||||
|
await server.pluginContainer.buildStart({});
|
||||||
|
const node = new ViteNodeServer(
|
||||||
|
// @ts-ignore: Some weird type error...
|
||||||
|
server,
|
||||||
|
);
|
||||||
|
installSourcemapsSupport({
|
||||||
|
getSourceMap: (source) => node.getSourceMap(source),
|
||||||
|
});
|
||||||
|
const runner = new ViteNodeRunner({
|
||||||
|
root: server.config.root,
|
||||||
|
base: server.config.base,
|
||||||
|
// when having the server and runner in a different context,
|
||||||
|
// you will need to handle the communication between them
|
||||||
|
// and pass to this function
|
||||||
|
fetchModule(id) {
|
||||||
|
return node.fetchModule(id);
|
||||||
|
},
|
||||||
|
resolveId(id, importer) {
|
||||||
|
return node.resolveId(id, importer);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const res = await runner.executeFile(path);
|
||||||
|
await server.close();
|
||||||
|
return res.default;
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
async build(group) {
|
async build(group) {
|
||||||
let entryConfig;
|
let entryConfig;
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ export * from './devHtmlPrerender';
|
|||||||
export * from './devServerGlobals';
|
export * from './devServerGlobals';
|
||||||
export * from './download';
|
export * from './download';
|
||||||
export * from './multipageMove';
|
export * from './multipageMove';
|
||||||
export * from './unimport';
|
|
||||||
export * from './resolveVirtualModules';
|
export * from './resolveVirtualModules';
|
||||||
export * from './tsconfigPaths';
|
export * from './tsconfigPaths';
|
||||||
export * from './noopBackground';
|
export * from './noopBackground';
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
import { createUnimport } from 'unimport';
|
|
||||||
import { ResolvedConfig } from '~/types';
|
|
||||||
import type * as vite from 'vite';
|
|
||||||
import { extname } from 'path';
|
|
||||||
|
|
||||||
const ENABLED_EXTENSIONS = new Set([
|
|
||||||
'.js',
|
|
||||||
'.jsx',
|
|
||||||
'.ts',
|
|
||||||
'.tsx',
|
|
||||||
'.vue',
|
|
||||||
'.svelte',
|
|
||||||
]);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inject any global imports defined by unimport
|
|
||||||
*/
|
|
||||||
export function unimport(config: ResolvedConfig): vite.PluginOption {
|
|
||||||
const options = config.imports;
|
|
||||||
if (options === false) return [];
|
|
||||||
|
|
||||||
const unimport = createUnimport(options);
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: 'wxt:unimport',
|
|
||||||
async config() {
|
|
||||||
await unimport.scanImportsFromDir(undefined, { cwd: config.srcDir });
|
|
||||||
},
|
|
||||||
async transform(code, id) {
|
|
||||||
// Don't transform dependencies
|
|
||||||
if (id.includes('node_modules')) return;
|
|
||||||
|
|
||||||
// Don't transform non-js files
|
|
||||||
if (!ENABLED_EXTENSIONS.has(extname(id))) return;
|
|
||||||
|
|
||||||
const injected = await unimport.injectImports(code, id);
|
|
||||||
return {
|
|
||||||
code: injected.code,
|
|
||||||
map: injected.s.generateMap({ hires: 'boundary', source: id }),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -46,13 +46,19 @@ try {
|
|||||||
// Use "pre" so the new script is added before vite bundles all the scripts
|
// Use "pre" so the new script is added before vite bundles all the scripts
|
||||||
order: 'pre',
|
order: 'pre',
|
||||||
handler(html, _ctx) {
|
handler(html, _ctx) {
|
||||||
const { document } = parseHTML(html);
|
const src =
|
||||||
const script = document.createElement('script');
|
|
||||||
script.type = 'module';
|
|
||||||
script.src =
|
|
||||||
config.command === 'serve'
|
config.command === 'serve'
|
||||||
? `http://${config.dev.server?.hostname}:${config.dev.server?.port}/@id/${virtualHtmlModuleId}`
|
? `http://${config.dev.server?.hostname}:${config.dev.server?.port}/@id/${virtualHtmlModuleId}`
|
||||||
: virtualHtmlModuleId;
|
: virtualHtmlModuleId;
|
||||||
|
|
||||||
|
const { document } = parseHTML(html);
|
||||||
|
const existing = document.querySelector(`script[src='${src}']`);
|
||||||
|
if (existing) return;
|
||||||
|
|
||||||
|
const script = document.createElement('script');
|
||||||
|
script.type = 'module';
|
||||||
|
script.src = src;
|
||||||
|
|
||||||
if (document.head == null) {
|
if (document.head == null) {
|
||||||
const newHead = document.createElement('head');
|
const newHead = document.createElement('head');
|
||||||
document.documentElement.prepend(newHead);
|
document.documentElement.prepend(newHead);
|
||||||
|
|||||||
@@ -1,44 +1,65 @@
|
|||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import glob from 'fast-glob';
|
import glob from 'fast-glob';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import { consola } from 'consola';
|
|
||||||
import pc from 'picocolors';
|
import pc from 'picocolors';
|
||||||
|
import { InlineConfig } from '~/types';
|
||||||
|
import { registerWxt, wxt } from './wxt';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove generated/temp files from the directory.
|
* 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.
|
* @param root The directory to look for generated/temp files in. Defaults to `process.cwd()`. Can be relative to `process.cwd()` or absolute.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* await clean();
|
* await clean();
|
||||||
*/
|
*/
|
||||||
export async function clean(root = process.cwd()) {
|
export async function clean(root?: string): Promise<void>;
|
||||||
consola.info('Cleaning Project');
|
|
||||||
|
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 = [
|
const tempDirs = [
|
||||||
'node_modules/.vite',
|
'node_modules/.vite',
|
||||||
'node_modules/.cache',
|
'node_modules/.cache',
|
||||||
'**/.wxt',
|
'**/.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, {
|
const directories = await glob(tempDirs, {
|
||||||
cwd: path.resolve(root),
|
cwd: root,
|
||||||
absolute: true,
|
absolute: true,
|
||||||
onlyDirectories: true,
|
onlyDirectories: true,
|
||||||
deep: 2,
|
deep: 2,
|
||||||
});
|
});
|
||||||
if (directories.length === 0) {
|
if (directories.length === 0) {
|
||||||
consola.debug('No generated files found.');
|
wxt.logger.debug('No generated files found.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
consola.debug(
|
wxt.logger.debug(
|
||||||
'Found:',
|
'Found:',
|
||||||
directories.map((dir) => pc.cyan(path.relative(root, dir))).join(', '),
|
directories.map((dir) => pc.cyan(path.relative(root, dir))).join(', '),
|
||||||
);
|
);
|
||||||
for (const directory of directories) {
|
for (const directory of directories) {
|
||||||
await fs.rm(directory, { force: true, recursive: true });
|
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)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import {
|
|||||||
findEntrypoints,
|
findEntrypoints,
|
||||||
} from '~/core/utils/building';
|
} from '~/core/utils/building';
|
||||||
import { createExtensionRunner } from '~/core/runners';
|
import { createExtensionRunner } from '~/core/runners';
|
||||||
import { consola } from 'consola';
|
|
||||||
import { Mutex } from 'async-mutex';
|
import { Mutex } from 'async-mutex';
|
||||||
import pc from 'picocolors';
|
import pc from 'picocolors';
|
||||||
import { relative } from 'node:path';
|
import { relative } from 'node:path';
|
||||||
@@ -153,6 +152,7 @@ function createFileReloader(server: WxtDevServer) {
|
|||||||
|
|
||||||
// Here, "path" is a non-normalized path (ie: C:\\users\\... instead of C:/users/...)
|
// Here, "path" is a non-normalized path (ie: C:\\users\\... instead of C:/users/...)
|
||||||
if (path.startsWith(wxt.config.outBaseDir)) return;
|
if (path.startsWith(wxt.config.outBaseDir)) return;
|
||||||
|
if (path.startsWith(wxt.config.wxtDir)) return;
|
||||||
changeQueue.push([event, path]);
|
changeQueue.push([event, path]);
|
||||||
|
|
||||||
await fileChangedMutex.runExclusive(async () => {
|
await fileChangedMutex.runExclusive(async () => {
|
||||||
@@ -200,21 +200,21 @@ function createFileReloader(server: WxtDevServer) {
|
|||||||
switch (changes.type) {
|
switch (changes.type) {
|
||||||
case 'extension-reload':
|
case 'extension-reload':
|
||||||
server.reloadExtension();
|
server.reloadExtension();
|
||||||
consola.success(`Reloaded extension`);
|
wxt.logger.success(`Reloaded extension`);
|
||||||
break;
|
break;
|
||||||
case 'html-reload':
|
case 'html-reload':
|
||||||
const { reloadedNames } = reloadHtmlPages(
|
const { reloadedNames } = reloadHtmlPages(
|
||||||
changes.rebuildGroups,
|
changes.rebuildGroups,
|
||||||
server,
|
server,
|
||||||
);
|
);
|
||||||
consola.success(`Reloaded: ${getFilenameList(reloadedNames)}`);
|
wxt.logger.success(`Reloaded: ${getFilenameList(reloadedNames)}`);
|
||||||
break;
|
break;
|
||||||
case 'content-script-reload':
|
case 'content-script-reload':
|
||||||
reloadContentScripts(changes.changedSteps, server);
|
reloadContentScripts(changes.changedSteps, server);
|
||||||
const rebuiltNames = changes.rebuildGroups
|
const rebuiltNames = changes.rebuildGroups
|
||||||
.flat()
|
.flat()
|
||||||
.map((entry) => entry.name);
|
.map((entry) => entry.name);
|
||||||
consola.success(`Reloaded: ${getFilenameList(rebuiltNames)}`);
|
wxt.logger.success(`Reloaded: ${getFilenameList(rebuiltNames)}`);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ import { describe, expect, it } from 'vitest';
|
|||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { bun } from '../bun';
|
import { bun } from '../bun';
|
||||||
|
|
||||||
const cwd = path.resolve(__dirname, 'fixtures/bun-project');
|
|
||||||
|
|
||||||
describe.skipIf(() => process.platform === 'win32')(
|
describe.skipIf(() => process.platform === 'win32')(
|
||||||
'Bun Package Management Utils',
|
'Bun Package Management Utils',
|
||||||
() => {
|
() => {
|
||||||
describe('listDependencies', () => {
|
describe('listDependencies', () => {
|
||||||
|
const cwd = path.resolve(__dirname, 'fixtures/simple-bun-project');
|
||||||
|
|
||||||
it('should list direct dependencies', async () => {
|
it('should list direct dependencies', async () => {
|
||||||
const actual = await bun.listDependencies({ cwd });
|
const actual = await bun.listDependencies({ cwd });
|
||||||
expect(actual).toEqual([
|
expect(actual).toEqual([
|
||||||
|
|||||||
@@ -4,15 +4,14 @@ import { npm } from '../npm';
|
|||||||
import { execaCommand } from 'execa';
|
import { execaCommand } from 'execa';
|
||||||
import { exists } from 'fs-extra';
|
import { exists } from 'fs-extra';
|
||||||
|
|
||||||
const cwd = path.resolve(__dirname, 'fixtures/npm-project');
|
|
||||||
|
|
||||||
describe('NPM Package Management Utils', () => {
|
describe('NPM Package Management Utils', () => {
|
||||||
beforeAll(async () => {
|
|
||||||
// NPM needs the modules installed for 'npm ls' to work
|
|
||||||
await execaCommand('npm i', { cwd });
|
|
||||||
}, 60e3);
|
|
||||||
|
|
||||||
describe('listDependencies', () => {
|
describe('listDependencies', () => {
|
||||||
|
const cwd = path.resolve(__dirname, 'fixtures/simple-npm-project');
|
||||||
|
beforeAll(async () => {
|
||||||
|
// NPM needs the modules installed for 'npm ls' to work
|
||||||
|
await execaCommand('npm i', { cwd });
|
||||||
|
}, 60e3);
|
||||||
|
|
||||||
it('should list direct dependencies', async () => {
|
it('should list direct dependencies', async () => {
|
||||||
const actual = await npm.listDependencies({ cwd });
|
const actual = await npm.listDependencies({ cwd });
|
||||||
expect(actual).toEqual([
|
expect(actual).toEqual([
|
||||||
@@ -32,6 +31,8 @@ describe('NPM Package Management Utils', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('downloadDependency', () => {
|
describe('downloadDependency', () => {
|
||||||
|
const cwd = path.resolve(__dirname, 'fixtures/simple-npm-project');
|
||||||
|
|
||||||
it('should download the dependency as a tarball', async () => {
|
it('should download the dependency as a tarball', async () => {
|
||||||
const downloadDir = path.resolve(cwd, 'dist');
|
const downloadDir = path.resolve(cwd, 'dist');
|
||||||
const id = 'mime-db@1.52.0';
|
const id = 'mime-db@1.52.0';
|
||||||
|
|||||||
@@ -3,17 +3,16 @@ import path from 'node:path';
|
|||||||
import { pnpm } from '../pnpm';
|
import { pnpm } from '../pnpm';
|
||||||
import { execaCommand } from 'execa';
|
import { execaCommand } from 'execa';
|
||||||
|
|
||||||
const cwd = path.resolve(__dirname, 'fixtures/pnpm-project');
|
|
||||||
|
|
||||||
process.env.WXT_PNPM_IGNORE_WORKSPACE = 'true';
|
process.env.WXT_PNPM_IGNORE_WORKSPACE = 'true';
|
||||||
|
|
||||||
describe('PNPM Package Management Utils', () => {
|
describe('PNPM Package Management Utils', () => {
|
||||||
beforeAll(async () => {
|
|
||||||
// PNPM needs the modules installed, or 'pnpm ls' will return a blank list.
|
|
||||||
await execaCommand('pnpm i --ignore-workspace', { cwd });
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('listDependencies', () => {
|
describe('listDependencies', () => {
|
||||||
|
const cwd = path.resolve(__dirname, 'fixtures/simple-pnpm-project');
|
||||||
|
beforeAll(async () => {
|
||||||
|
// PNPM needs the modules installed, or 'pnpm ls' will return a blank list.
|
||||||
|
await execaCommand('pnpm i --ignore-workspace', { cwd });
|
||||||
|
});
|
||||||
|
|
||||||
it('should list direct dependencies', async () => {
|
it('should list direct dependencies', async () => {
|
||||||
const actual = await pnpm.listDependencies({ cwd });
|
const actual = await pnpm.listDependencies({ cwd });
|
||||||
expect(actual).toEqual([
|
expect(actual).toEqual([
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import { describe, expect, it } from 'vitest';
|
|||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { yarn } from '../yarn';
|
import { yarn } from '../yarn';
|
||||||
|
|
||||||
const cwd = path.resolve(__dirname, 'fixtures/yarn-project');
|
|
||||||
|
|
||||||
describe('Yarn Package Management Utils', () => {
|
describe('Yarn Package Management Utils', () => {
|
||||||
describe('listDependencies', () => {
|
describe('listDependencies', () => {
|
||||||
|
const cwd = path.resolve(__dirname, 'fixtures/simple-yarn-project');
|
||||||
|
|
||||||
it('should list direct dependencies', async () => {
|
it('should list direct dependencies', async () => {
|
||||||
const actual = await yarn.listDependencies({ cwd });
|
const actual = await yarn.listDependencies({ cwd });
|
||||||
expect(actual).toEqual([
|
expect(actual).toEqual([
|
||||||
|
|||||||
@@ -19,8 +19,7 @@ export const bun: WxtPackageManagerImpl = {
|
|||||||
.slice(1) // Skip the first line, is not a dependency
|
.slice(1) // Skip the first line, is not a dependency
|
||||||
.map((line) => line.trim())
|
.map((line) => line.trim())
|
||||||
.map((line) => /.* (@?\S+)@(\S+)$/.exec(line))
|
.map((line) => /.* (@?\S+)@(\S+)$/.exec(line))
|
||||||
// @ts-expect-error: Filtering to known non-null matches
|
.filter((match) => !!match)
|
||||||
.filter<RegExpExecArray>((match) => !!match)
|
|
||||||
.map(([_, name, version]) => ({ name, version })),
|
.map(([_, name, version]) => ({ name, version })),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -100,6 +100,11 @@ describe('Detect Dev Changes', () => {
|
|||||||
describe('Public Assets', () => {
|
describe('Public Assets', () => {
|
||||||
it("should return 'extension-reload' without any groups to rebuild when the changed file is a public asset", () => {
|
it("should return 'extension-reload' without any groups to rebuild when the changed file is a public asset", () => {
|
||||||
const changes = ['/root/src/public/image.svg'];
|
const changes = ['/root/src/public/image.svg'];
|
||||||
|
setFakeWxt({
|
||||||
|
config: {
|
||||||
|
publicDir: '/root/src/public',
|
||||||
|
},
|
||||||
|
});
|
||||||
const asset1 = fakeOutputAsset({
|
const asset1 = fakeOutputAsset({
|
||||||
fileName: 'image.svg',
|
fileName: 'image.svg',
|
||||||
});
|
});
|
||||||
@@ -114,10 +119,7 @@ describe('Detect Dev Changes', () => {
|
|||||||
const expected: DevModeChange = {
|
const expected: DevModeChange = {
|
||||||
type: 'extension-reload',
|
type: 'extension-reload',
|
||||||
rebuildGroups: [],
|
rebuildGroups: [],
|
||||||
cachedOutput: {
|
cachedOutput: currentOutput,
|
||||||
...currentOutput,
|
|
||||||
publicAssets: [asset2],
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const actual = detectDevChanges(changes, currentOutput);
|
const actual = detectDevChanges(changes, currentOutput);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
import { describe, it, expect, vi, beforeEach, Mock } from 'vitest';
|
||||||
import {
|
import {
|
||||||
BackgroundEntrypoint,
|
BackgroundEntrypoint,
|
||||||
BackgroundEntrypointOptions,
|
BackgroundEntrypointOptions,
|
||||||
@@ -12,16 +12,13 @@ import {
|
|||||||
import { resolve } from 'path';
|
import { resolve } from 'path';
|
||||||
import { findEntrypoints } from '../find-entrypoints';
|
import { findEntrypoints } from '../find-entrypoints';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import { importEntrypointFile } from '../import-entrypoint';
|
|
||||||
import glob from 'fast-glob';
|
import glob from 'fast-glob';
|
||||||
import {
|
import {
|
||||||
fakeResolvedConfig,
|
fakeResolvedConfig,
|
||||||
setFakeWxt,
|
setFakeWxt,
|
||||||
} from '~/core/utils/testing/fake-objects';
|
} from '~/core/utils/testing/fake-objects';
|
||||||
import { unnormalizePath } from '~/core/utils/paths';
|
import { unnormalizePath } from '~/core/utils/paths';
|
||||||
|
import { wxt } from '~/core/wxt';
|
||||||
vi.mock('../import-entrypoint');
|
|
||||||
const importEntrypointFileMock = vi.mocked(importEntrypointFile);
|
|
||||||
|
|
||||||
vi.mock('fast-glob');
|
vi.mock('fast-glob');
|
||||||
const globMock = vi.mocked(glob);
|
const globMock = vi.mocked(glob);
|
||||||
@@ -39,9 +36,11 @@ describe('findEntrypoints', () => {
|
|||||||
outDir: resolve('.output'),
|
outDir: resolve('.output'),
|
||||||
command: 'build',
|
command: 'build',
|
||||||
});
|
});
|
||||||
|
let importEntrypointMock: Mock;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
setFakeWxt({ config });
|
setFakeWxt({ config });
|
||||||
|
importEntrypointMock = vi.mocked(wxt.builder.importEntrypoint);
|
||||||
});
|
});
|
||||||
|
|
||||||
it.each<[string, string, PopupEntrypoint]>([
|
it.each<[string, string, PopupEntrypoint]>([
|
||||||
@@ -211,13 +210,13 @@ describe('findEntrypoints', () => {
|
|||||||
matches: ['<all_urls>'],
|
matches: ['<all_urls>'],
|
||||||
};
|
};
|
||||||
globMock.mockResolvedValueOnce([path]);
|
globMock.mockResolvedValueOnce([path]);
|
||||||
importEntrypointFileMock.mockResolvedValue(options);
|
importEntrypointMock.mockResolvedValue(options);
|
||||||
|
|
||||||
const entrypoints = await findEntrypoints();
|
const entrypoints = await findEntrypoints();
|
||||||
|
|
||||||
expect(entrypoints).toHaveLength(1);
|
expect(entrypoints).toHaveLength(1);
|
||||||
expect(entrypoints[0]).toEqual({ ...expected, options });
|
expect(entrypoints[0]).toEqual({ ...expected, options });
|
||||||
expect(importEntrypointFileMock).toBeCalledWith(expected.inputPath);
|
expect(importEntrypointMock).toBeCalledWith(expected.inputPath);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -249,13 +248,13 @@ describe('findEntrypoints', () => {
|
|||||||
type: 'module',
|
type: 'module',
|
||||||
};
|
};
|
||||||
globMock.mockResolvedValueOnce([path]);
|
globMock.mockResolvedValueOnce([path]);
|
||||||
importEntrypointFileMock.mockResolvedValue(options);
|
importEntrypointMock.mockResolvedValue(options);
|
||||||
|
|
||||||
const entrypoints = await findEntrypoints();
|
const entrypoints = await findEntrypoints();
|
||||||
|
|
||||||
expect(entrypoints).toHaveLength(1);
|
expect(entrypoints).toHaveLength(1);
|
||||||
expect(entrypoints[0]).toEqual({ ...expected, options });
|
expect(entrypoints[0]).toEqual({ ...expected, options });
|
||||||
expect(importEntrypointFileMock).toBeCalledWith(expected.inputPath);
|
expect(importEntrypointMock).toBeCalledWith(expected.inputPath);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -338,12 +337,13 @@ describe('findEntrypoints', () => {
|
|||||||
config: {
|
config: {
|
||||||
manifestVersion: 2,
|
manifestVersion: 2,
|
||||||
},
|
},
|
||||||
|
builder: wxt.builder,
|
||||||
});
|
});
|
||||||
const options: BackgroundEntrypointOptions = {
|
const options: BackgroundEntrypointOptions = {
|
||||||
type: 'module',
|
type: 'module',
|
||||||
};
|
};
|
||||||
globMock.mockResolvedValueOnce(['background.ts']);
|
globMock.mockResolvedValueOnce(['background.ts']);
|
||||||
importEntrypointFileMock.mockResolvedValue(options);
|
importEntrypointMock.mockResolvedValue(options);
|
||||||
|
|
||||||
const entrypoints = await findEntrypoints();
|
const entrypoints = await findEntrypoints();
|
||||||
|
|
||||||
@@ -355,12 +355,13 @@ describe('findEntrypoints', () => {
|
|||||||
config: {
|
config: {
|
||||||
manifestVersion: 3,
|
manifestVersion: 3,
|
||||||
},
|
},
|
||||||
|
builder: wxt.builder,
|
||||||
});
|
});
|
||||||
const options: BackgroundEntrypointOptions = {
|
const options: BackgroundEntrypointOptions = {
|
||||||
type: 'module',
|
type: 'module',
|
||||||
};
|
};
|
||||||
globMock.mockResolvedValueOnce(['background.ts']);
|
globMock.mockResolvedValueOnce(['background.ts']);
|
||||||
importEntrypointFileMock.mockResolvedValue(options);
|
importEntrypointMock.mockResolvedValue(options);
|
||||||
|
|
||||||
const entrypoints = await findEntrypoints();
|
const entrypoints = await findEntrypoints();
|
||||||
|
|
||||||
@@ -373,6 +374,7 @@ describe('findEntrypoints', () => {
|
|||||||
...config,
|
...config,
|
||||||
command: 'serve',
|
command: 'serve',
|
||||||
},
|
},
|
||||||
|
builder: wxt.builder,
|
||||||
});
|
});
|
||||||
globMock.mockResolvedValueOnce(['popup.html']);
|
globMock.mockResolvedValueOnce(['popup.html']);
|
||||||
|
|
||||||
@@ -410,13 +412,13 @@ describe('findEntrypoints', () => {
|
|||||||
};
|
};
|
||||||
const options: BaseEntrypointOptions = {};
|
const options: BaseEntrypointOptions = {};
|
||||||
globMock.mockResolvedValueOnce([path]);
|
globMock.mockResolvedValueOnce([path]);
|
||||||
importEntrypointFileMock.mockResolvedValue(options);
|
importEntrypointMock.mockResolvedValue(options);
|
||||||
|
|
||||||
const entrypoints = await findEntrypoints();
|
const entrypoints = await findEntrypoints();
|
||||||
|
|
||||||
expect(entrypoints).toHaveLength(1);
|
expect(entrypoints).toHaveLength(1);
|
||||||
expect(entrypoints[0]).toEqual({ ...expected, options });
|
expect(entrypoints[0]).toEqual({ ...expected, options });
|
||||||
expect(importEntrypointFileMock).toBeCalledWith(expected.inputPath);
|
expect(importEntrypointMock).toBeCalledWith(expected.inputPath);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -701,7 +703,7 @@ describe('findEntrypoints', () => {
|
|||||||
describe('include option', () => {
|
describe('include option', () => {
|
||||||
it("should filter out the background when include doesn't contain the target browser", async () => {
|
it("should filter out the background when include doesn't contain the target browser", async () => {
|
||||||
globMock.mockResolvedValueOnce(['background.ts']);
|
globMock.mockResolvedValueOnce(['background.ts']);
|
||||||
importEntrypointFileMock.mockResolvedValue({
|
importEntrypointMock.mockResolvedValue({
|
||||||
include: ['not' + config.browser],
|
include: ['not' + config.browser],
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -712,7 +714,7 @@ describe('findEntrypoints', () => {
|
|||||||
|
|
||||||
it("should filter out content scripts when include doesn't contain the target browser", async () => {
|
it("should filter out content scripts when include doesn't contain the target browser", async () => {
|
||||||
globMock.mockResolvedValueOnce(['example.content.ts']);
|
globMock.mockResolvedValueOnce(['example.content.ts']);
|
||||||
importEntrypointFileMock.mockResolvedValue({
|
importEntrypointMock.mockResolvedValue({
|
||||||
include: ['not' + config.browser],
|
include: ['not' + config.browser],
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -776,7 +778,7 @@ describe('findEntrypoints', () => {
|
|||||||
describe('exclude option', () => {
|
describe('exclude option', () => {
|
||||||
it('should filter out the background when exclude contains the target browser', async () => {
|
it('should filter out the background when exclude contains the target browser', async () => {
|
||||||
globMock.mockResolvedValueOnce(['background.ts']);
|
globMock.mockResolvedValueOnce(['background.ts']);
|
||||||
importEntrypointFileMock.mockResolvedValue({
|
importEntrypointMock.mockResolvedValue({
|
||||||
exclude: [config.browser],
|
exclude: [config.browser],
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -787,7 +789,7 @@ describe('findEntrypoints', () => {
|
|||||||
|
|
||||||
it('should filter out content scripts when exclude contains the target browser', async () => {
|
it('should filter out content scripts when exclude contains the target browser', async () => {
|
||||||
globMock.mockResolvedValueOnce(['example.content.ts']);
|
globMock.mockResolvedValueOnce(['example.content.ts']);
|
||||||
importEntrypointFileMock.mockResolvedValue({
|
importEntrypointMock.mockResolvedValue({
|
||||||
exclude: [config.browser],
|
exclude: [config.browser],
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -850,7 +852,6 @@ describe('findEntrypoints', () => {
|
|||||||
'ui.content/index.ts',
|
'ui.content/index.ts',
|
||||||
'injected.content/index.ts',
|
'injected.content/index.ts',
|
||||||
]);
|
]);
|
||||||
importEntrypointFileMock.mockResolvedValue({});
|
|
||||||
const filterEntrypoints = ['popup', 'ui'];
|
const filterEntrypoints = ['popup', 'ui'];
|
||||||
setFakeWxt({
|
setFakeWxt({
|
||||||
config: {
|
config: {
|
||||||
@@ -860,8 +861,11 @@ describe('findEntrypoints', () => {
|
|||||||
command: 'build',
|
command: 'build',
|
||||||
filterEntrypoints: new Set(filterEntrypoints),
|
filterEntrypoints: new Set(filterEntrypoints),
|
||||||
},
|
},
|
||||||
|
builder: wxt.builder,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
importEntrypointMock.mockResolvedValue({});
|
||||||
|
|
||||||
const entrypoints = await findEntrypoints();
|
const entrypoints = await findEntrypoints();
|
||||||
const names = entrypoints.map((item) => item.name);
|
const names = entrypoints.map((item) => item.name);
|
||||||
expect(names).toHaveLength(2);
|
expect(names).toHaveLength(2);
|
||||||
|
|||||||
@@ -45,14 +45,18 @@ async function copyPublicDirectory(): Promise<BuildOutput['publicAssets']> {
|
|||||||
if (files.length === 0) return [];
|
if (files.length === 0) return [];
|
||||||
|
|
||||||
const publicAssets: BuildOutput['publicAssets'] = [];
|
const publicAssets: BuildOutput['publicAssets'] = [];
|
||||||
for (const { absoluteSrc, relativeDest } of files) {
|
for (const file of files) {
|
||||||
const absoluteDest = resolve(wxt.config.outDir, relativeDest);
|
const absoluteDest = resolve(wxt.config.outDir, file.relativeDest);
|
||||||
|
|
||||||
await fs.ensureDir(dirname(absoluteDest));
|
await fs.ensureDir(dirname(absoluteDest));
|
||||||
await fs.copyFile(absoluteSrc, absoluteDest);
|
if ('absoluteSrc' in file) {
|
||||||
|
await fs.copyFile(file.absoluteSrc, absoluteDest);
|
||||||
|
} else {
|
||||||
|
await fs.writeFile(absoluteDest, file.contents, 'utf8');
|
||||||
|
}
|
||||||
publicAssets.push({
|
publicAssets.push({
|
||||||
type: 'asset',
|
type: 'asset',
|
||||||
fileName: relativeDest,
|
fileName: file.relativeDest,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import {
|
|||||||
BuildOutput,
|
BuildOutput,
|
||||||
BuildStepOutput,
|
BuildStepOutput,
|
||||||
EntrypointGroup,
|
EntrypointGroup,
|
||||||
OutputAsset,
|
|
||||||
OutputFile,
|
OutputFile,
|
||||||
} from '~/types';
|
} from '~/types';
|
||||||
import { every, some } from '~/core/utils/arrays';
|
import { every, some } from '~/core/utils/arrays';
|
||||||
@@ -50,12 +49,25 @@ export function detectDevChanges(
|
|||||||
findEffectedSteps(changedFile, currentOutput),
|
findEffectedSteps(changedFile, currentOutput),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (changedSteps.size === 0) return { type: 'no-change' };
|
if (changedSteps.size === 0) {
|
||||||
|
const hasPublicChange = some(changedFiles, (file) =>
|
||||||
|
file.startsWith(wxt.config.publicDir),
|
||||||
|
);
|
||||||
|
if (hasPublicChange) {
|
||||||
|
return {
|
||||||
|
type: 'extension-reload',
|
||||||
|
rebuildGroups: [],
|
||||||
|
cachedOutput: currentOutput,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return { type: 'no-change' };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const unchangedOutput: BuildOutput = {
|
const unchangedOutput: BuildOutput = {
|
||||||
manifest: currentOutput.manifest,
|
manifest: currentOutput.manifest,
|
||||||
steps: [],
|
steps: [],
|
||||||
publicAssets: [],
|
publicAssets: [...currentOutput.publicAssets],
|
||||||
};
|
};
|
||||||
const changedOutput: BuildOutput = {
|
const changedOutput: BuildOutput = {
|
||||||
manifest: currentOutput.manifest,
|
manifest: currentOutput.manifest,
|
||||||
@@ -70,13 +82,6 @@ export function detectDevChanges(
|
|||||||
unchangedOutput.steps.push(step);
|
unchangedOutput.steps.push(step);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const asset of currentOutput.publicAssets) {
|
|
||||||
if (changedSteps.has(asset)) {
|
|
||||||
changedOutput.publicAssets.push(asset);
|
|
||||||
} else {
|
|
||||||
unchangedOutput.publicAssets.push(asset);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const isOnlyHtmlChanges =
|
const isOnlyHtmlChanges =
|
||||||
changedFiles.length > 0 &&
|
changedFiles.length > 0 &&
|
||||||
@@ -117,8 +122,8 @@ export function detectDevChanges(
|
|||||||
function findEffectedSteps(
|
function findEffectedSteps(
|
||||||
changedFile: string,
|
changedFile: string,
|
||||||
currentOutput: BuildOutput,
|
currentOutput: BuildOutput,
|
||||||
): DetectedChange[] {
|
): BuildStepOutput[] {
|
||||||
const changes: DetectedChange[] = [];
|
const changes: BuildStepOutput[] = [];
|
||||||
const changedPath = normalizePath(changedFile);
|
const changedPath = normalizePath(changedFile);
|
||||||
|
|
||||||
const isChunkEffected = (chunk: OutputFile): boolean =>
|
const isChunkEffected = (chunk: OutputFile): boolean =>
|
||||||
@@ -135,11 +140,6 @@ function findEffectedSteps(
|
|||||||
if (effectedChunk) changes.push(step);
|
if (effectedChunk) changes.push(step);
|
||||||
}
|
}
|
||||||
|
|
||||||
const effectedAsset = currentOutput.publicAssets.find((chunk) =>
|
|
||||||
isChunkEffected(chunk),
|
|
||||||
);
|
|
||||||
if (effectedAsset) changes.push(effectedAsset);
|
|
||||||
|
|
||||||
return changes;
|
return changes;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,10 +194,3 @@ interface ContentScriptReload extends RebuildChange {
|
|||||||
type: 'content-script-reload';
|
type: 'content-script-reload';
|
||||||
changedSteps: BuildStepOutput[];
|
changedSteps: BuildStepOutput[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* When figuring out what needs reloaded, this stores the step that was changed, or the public
|
|
||||||
* directory asset that was changed. It doesn't know what type of change is required yet. Just an
|
|
||||||
* intermediate type.
|
|
||||||
*/
|
|
||||||
type DetectedChange = BuildStepOutput | OutputAsset;
|
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ import { VIRTUAL_NOOP_BACKGROUND_MODULE_ID } from '~/core/utils/constants';
|
|||||||
import { CSS_EXTENSIONS_PATTERN } from '~/core/utils/paths';
|
import { CSS_EXTENSIONS_PATTERN } from '~/core/utils/paths';
|
||||||
import pc from 'picocolors';
|
import pc from 'picocolors';
|
||||||
import { wxt } from '../../wxt';
|
import { wxt } from '../../wxt';
|
||||||
import { importEntrypointFile } from './import-entrypoint';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return entrypoints and their configuration by looking through the project's files.
|
* Return entrypoints and their configuration by looking through the project's files.
|
||||||
@@ -290,7 +289,7 @@ async function getUnlistedScriptEntrypoint({
|
|||||||
skipped,
|
skipped,
|
||||||
}: EntrypointInfo): Promise<GenericEntrypoint> {
|
}: EntrypointInfo): Promise<GenericEntrypoint> {
|
||||||
const defaultExport =
|
const defaultExport =
|
||||||
await importEntrypoint<UnlistedScriptDefinition>(inputPath);
|
await wxt.builder.importEntrypoint<UnlistedScriptDefinition>(inputPath);
|
||||||
if (defaultExport == null) {
|
if (defaultExport == null) {
|
||||||
throw Error(
|
throw Error(
|
||||||
`${name}: Default export not found, did you forget to call "export default defineUnlistedScript(...)"?`,
|
`${name}: Default export not found, did you forget to call "export default defineUnlistedScript(...)"?`,
|
||||||
@@ -315,7 +314,7 @@ async function getBackgroundEntrypoint({
|
|||||||
let options: Omit<BackgroundDefinition, 'main'> = {};
|
let options: Omit<BackgroundDefinition, 'main'> = {};
|
||||||
if (inputPath !== VIRTUAL_NOOP_BACKGROUND_MODULE_ID) {
|
if (inputPath !== VIRTUAL_NOOP_BACKGROUND_MODULE_ID) {
|
||||||
const defaultExport =
|
const defaultExport =
|
||||||
await importEntrypoint<BackgroundDefinition>(inputPath);
|
await wxt.builder.importEntrypoint<BackgroundDefinition>(inputPath);
|
||||||
if (defaultExport == null) {
|
if (defaultExport == null) {
|
||||||
throw Error(
|
throw Error(
|
||||||
`${name}: Default export not found, did you forget to call "export default defineBackground(...)"?`,
|
`${name}: Default export not found, did you forget to call "export default defineBackground(...)"?`,
|
||||||
@@ -344,8 +343,15 @@ async function getContentScriptEntrypoint({
|
|||||||
name,
|
name,
|
||||||
skipped,
|
skipped,
|
||||||
}: EntrypointInfo): Promise<ContentScriptEntrypoint> {
|
}: EntrypointInfo): Promise<ContentScriptEntrypoint> {
|
||||||
const { main: _, ...options } =
|
const defaultExport =
|
||||||
await importEntrypoint<ContentScriptDefinition>(inputPath);
|
await wxt.builder.importEntrypoint<ContentScriptDefinition>(inputPath);
|
||||||
|
if (defaultExport == null) {
|
||||||
|
throw Error(
|
||||||
|
`${name}: Default export not found, did you forget to call "export default defineContentScript(...)"?`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { main: _, ...options } = defaultExport;
|
||||||
if (options == null) {
|
if (options == null) {
|
||||||
throw Error(
|
throw Error(
|
||||||
`${name}: Default export not found, did you forget to call "export default defineContentScript(...)"?`,
|
`${name}: Default export not found, did you forget to call "export default defineContentScript(...)"?`,
|
||||||
@@ -488,9 +494,3 @@ const PATH_GLOB_TO_TYPE_MAP: Record<string, Entrypoint['type']> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const CONTENT_SCRIPT_OUT_DIR = 'content-scripts';
|
const CONTENT_SCRIPT_OUT_DIR = 'content-scripts';
|
||||||
|
|
||||||
function importEntrypoint<T>(path: string) {
|
|
||||||
return wxt.config.experimental.viteRuntime
|
|
||||||
? wxt.builder.importEntrypoint<T>(path)
|
|
||||||
: importEntrypointFile<T>(path);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
import { Unimport, createUnimport } from 'unimport';
|
import { Entrypoint, WxtDirEntry, WxtDirFileEntry } from '~/types';
|
||||||
import {
|
|
||||||
EslintGlobalsPropValue,
|
|
||||||
Entrypoint,
|
|
||||||
WxtResolvedUnimportOptions,
|
|
||||||
} from '~/types';
|
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import { relative, resolve } from 'path';
|
import { dirname, relative, resolve } from 'node:path';
|
||||||
import {
|
import {
|
||||||
getEntrypointBundlePath,
|
getEntrypointBundlePath,
|
||||||
isHtmlEntrypoint,
|
isHtmlEntrypoint,
|
||||||
@@ -25,61 +20,57 @@ export async function generateTypesDir(
|
|||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await fs.ensureDir(wxt.config.typesDir);
|
await fs.ensureDir(wxt.config.typesDir);
|
||||||
|
|
||||||
const references: string[] = [];
|
const entries: WxtDirEntry[] = [
|
||||||
|
// Hard-coded entries
|
||||||
|
{ module: 'wxt/vite-builder-env' },
|
||||||
|
];
|
||||||
|
|
||||||
if (wxt.config.imports !== false) {
|
// Add references to modules installed from NPM to the TS project so their
|
||||||
const unimport = createUnimport(wxt.config.imports);
|
// type augmentation can update InlineConfig correctly. Local modules defined
|
||||||
references.push(await writeImportsDeclarationFile(unimport));
|
// in <root>/modules are already apart of the project, so we don't need to
|
||||||
if (wxt.config.imports.eslintrc.enabled) {
|
// add them.
|
||||||
await writeImportsEslintFile(unimport, wxt.config.imports);
|
wxt.config.userModules.forEach((module) => {
|
||||||
}
|
if (module.type === 'node_module' && module.configKey != null)
|
||||||
}
|
entries.push({ module: module.id });
|
||||||
|
});
|
||||||
|
|
||||||
references.push(await writePathsDeclarationFile(entrypoints));
|
// browser.runtime.getURL
|
||||||
references.push(await writeI18nDeclarationFile());
|
entries.push(await getPathsDeclarationEntry(entrypoints));
|
||||||
references.push(await writeGlobalsDeclarationFile());
|
|
||||||
|
|
||||||
const mainReference = await writeMainDeclarationFile(references);
|
// browser.i18n.getMessage
|
||||||
await writeTsConfigFile(mainReference);
|
entries.push(await getI18nDeclarationEntry());
|
||||||
}
|
|
||||||
|
|
||||||
async function writeImportsDeclarationFile(unimport: Unimport) {
|
// import.meta.env.*
|
||||||
const filePath = resolve(wxt.config.typesDir, 'imports.d.ts');
|
entries.push(await getGlobalsDeclarationEntry());
|
||||||
|
|
||||||
// Load project imports into unimport memory so they are output via generateTypeDeclarations
|
// tsconfig.json
|
||||||
await unimport.scanImportsFromDir(undefined, { cwd: wxt.config.srcDir });
|
entries.push(await getTsConfigEntry());
|
||||||
|
|
||||||
await writeFileIfDifferent(
|
// Let modules add more entries
|
||||||
filePath,
|
await wxt.hooks.callHook('prepare:types', wxt, entries);
|
||||||
['// Generated by wxt', await unimport.generateTypeDeclarations()].join(
|
|
||||||
'\n',
|
// Add main declaration file, not editable
|
||||||
) + '\n',
|
entries.push(getMainDeclarationEntry(entries));
|
||||||
|
|
||||||
|
// Write all the files
|
||||||
|
const absoluteFileEntries = (
|
||||||
|
entries.filter((entry) => 'path' in entry) as WxtDirFileEntry[]
|
||||||
|
).map<WxtDirFileEntry>((entry) => ({
|
||||||
|
...entry,
|
||||||
|
path: resolve(wxt.config.wxtDir, entry.path),
|
||||||
|
}));
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
absoluteFileEntries.map(async (file) => {
|
||||||
|
await fs.ensureDir(dirname(file.path));
|
||||||
|
await writeFileIfDifferent(file.path, file.text);
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
return filePath;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function writeImportsEslintFile(
|
async function getPathsDeclarationEntry(
|
||||||
unimport: Unimport,
|
|
||||||
options: WxtResolvedUnimportOptions,
|
|
||||||
) {
|
|
||||||
const globals: Record<string, EslintGlobalsPropValue> = {};
|
|
||||||
const eslintrc = { globals };
|
|
||||||
|
|
||||||
(await unimport.getImports())
|
|
||||||
.map((i) => i.as ?? i.name)
|
|
||||||
.filter(Boolean)
|
|
||||||
.sort()
|
|
||||||
.forEach((name) => {
|
|
||||||
eslintrc.globals[name] = options.eslintrc.globalsPropValue;
|
|
||||||
});
|
|
||||||
await fs.writeJson(options.eslintrc.filePath, eslintrc, { spaces: 2 });
|
|
||||||
}
|
|
||||||
|
|
||||||
async function writePathsDeclarationFile(
|
|
||||||
entrypoints: Entrypoint[],
|
entrypoints: Entrypoint[],
|
||||||
): Promise<string> {
|
): Promise<WxtDirFileEntry> {
|
||||||
const filePath = resolve(wxt.config.typesDir, 'paths.d.ts');
|
|
||||||
const unions = entrypoints
|
const unions = entrypoints
|
||||||
.map((entry) =>
|
.map((entry) =>
|
||||||
getEntrypointBundlePath(
|
getEntrypointBundlePath(
|
||||||
@@ -108,16 +99,14 @@ declare module "wxt/browser" {
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
await writeFileIfDifferent(
|
return {
|
||||||
filePath,
|
path: 'types/paths.d.ts',
|
||||||
template.replace('{{ union }}', unions || ' | never'),
|
text: template.replace('{{ union }}', unions || ' | never'),
|
||||||
);
|
tsReference: true,
|
||||||
|
};
|
||||||
return filePath;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function writeI18nDeclarationFile(): Promise<string> {
|
async function getI18nDeclarationEntry(): Promise<WxtDirFileEntry> {
|
||||||
const filePath = resolve(wxt.config.typesDir, 'i18n.d.ts');
|
|
||||||
const defaultLocale = wxt.config.manifest.default_locale;
|
const defaultLocale = wxt.config.manifest.default_locale;
|
||||||
const template = `// Generated by wxt
|
const template = `// Generated by wxt
|
||||||
import "wxt/browser";
|
import "wxt/browser";
|
||||||
@@ -165,20 +154,19 @@ declare module "wxt/browser" {
|
|||||||
options?: GetMessageOptions,
|
options?: GetMessageOptions,
|
||||||
): string;`;
|
): string;`;
|
||||||
});
|
});
|
||||||
await writeFileIfDifferent(
|
|
||||||
filePath,
|
|
||||||
template.replace('{{ overrides }}', overrides.join('\n')),
|
|
||||||
);
|
|
||||||
|
|
||||||
return filePath;
|
return {
|
||||||
|
path: 'types/i18n.d.ts',
|
||||||
|
text: template.replace('{{ overrides }}', overrides.join('\n')),
|
||||||
|
tsReference: true,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function writeGlobalsDeclarationFile(): Promise<string> {
|
async function getGlobalsDeclarationEntry(): Promise<WxtDirFileEntry> {
|
||||||
const filePath = resolve(wxt.config.typesDir, 'globals.d.ts');
|
|
||||||
const globals = [...getGlobals(wxt.config), ...getEntrypointGlobals('')];
|
const globals = [...getGlobals(wxt.config), ...getEntrypointGlobals('')];
|
||||||
await writeFileIfDifferent(
|
return {
|
||||||
filePath,
|
path: 'types/globals.d.ts',
|
||||||
[
|
text: [
|
||||||
'// Generated by wxt',
|
'// Generated by wxt',
|
||||||
'export {}',
|
'export {}',
|
||||||
'interface ImportMetaEnv {',
|
'interface ImportMetaEnv {',
|
||||||
@@ -187,39 +175,30 @@ async function writeGlobalsDeclarationFile(): Promise<string> {
|
|||||||
'interface ImportMeta {',
|
'interface ImportMeta {',
|
||||||
' readonly env: ImportMetaEnv',
|
' readonly env: ImportMetaEnv',
|
||||||
'}',
|
'}',
|
||||||
].join('\n') + '\n',
|
'',
|
||||||
);
|
].join('\n'),
|
||||||
return filePath;
|
tsReference: true,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function writeMainDeclarationFile(references: string[]): Promise<string> {
|
function getMainDeclarationEntry(references: WxtDirEntry[]): WxtDirFileEntry {
|
||||||
const dir = wxt.config.wxtDir;
|
const lines = ['// Generated by wxt'];
|
||||||
const filePath = resolve(dir, 'wxt.d.ts');
|
references.forEach((ref) => {
|
||||||
await writeFileIfDifferent(
|
if ('module' in ref) {
|
||||||
filePath,
|
return lines.push(`/// <reference types="${ref.module}" />`);
|
||||||
[
|
} else if (ref.tsReference) {
|
||||||
'// Generated by wxt',
|
const absolutePath = resolve(wxt.config.wxtDir, ref.path);
|
||||||
`/// <reference types="wxt/vite-builder-env" />`,
|
const relativePath = relative(wxt.config.wxtDir, absolutePath);
|
||||||
...references.map(
|
lines.push(`/// <reference types="./${normalizePath(relativePath)}" />`);
|
||||||
(ref) =>
|
}
|
||||||
`/// <reference types="./${normalizePath(relative(dir, ref))}" />`,
|
});
|
||||||
),
|
return {
|
||||||
|
path: 'wxt.d.ts',
|
||||||
// Add references to modules installed from NPM to the TS project so
|
text: lines.join('\n') + '\n',
|
||||||
// their type augmentation can update InlineConfig correctly. Local
|
};
|
||||||
// modules defined in <root>/modules are already apart of the project, so
|
|
||||||
// we don't need to add them.
|
|
||||||
...wxt.config.modules
|
|
||||||
.filter(
|
|
||||||
(module) => module.type === 'node_module' && module.configKey != null,
|
|
||||||
)
|
|
||||||
.map((module) => `/// <reference types="${module.id}" />`),
|
|
||||||
].join('\n') + '\n',
|
|
||||||
);
|
|
||||||
return filePath;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function writeTsConfigFile(mainReference: string) {
|
async function getTsConfigEntry(): Promise<WxtDirFileEntry> {
|
||||||
const dir = wxt.config.wxtDir;
|
const dir = wxt.config.wxtDir;
|
||||||
const getTsconfigPath = (path: string) => normalizePath(relative(dir, path));
|
const getTsconfigPath = (path: string) => normalizePath(relative(dir, path));
|
||||||
const paths = Object.entries(wxt.config.alias)
|
const paths = Object.entries(wxt.config.alias)
|
||||||
@@ -232,9 +211,7 @@ async function writeTsConfigFile(mainReference: string) {
|
|||||||
})
|
})
|
||||||
.join(',\n');
|
.join(',\n');
|
||||||
|
|
||||||
await writeFileIfDifferent(
|
const text = `{
|
||||||
resolve(dir, 'tsconfig.json'),
|
|
||||||
`{
|
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ESNext",
|
"target": "ESNext",
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
@@ -251,9 +228,13 @@ ${paths}
|
|||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"${getTsconfigPath(wxt.config.root)}/**/*",
|
"${getTsconfigPath(wxt.config.root)}/**/*",
|
||||||
"./${getTsconfigPath(mainReference)}"
|
"./wxt.d.ts"
|
||||||
],
|
],
|
||||||
"exclude": ["${getTsconfigPath(wxt.config.outBaseDir)}"]
|
"exclude": ["${getTsconfigPath(wxt.config.outBaseDir)}"]
|
||||||
}`,
|
}`;
|
||||||
);
|
|
||||||
|
return {
|
||||||
|
path: 'tsconfig.json',
|
||||||
|
text,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import {
|
|||||||
ValidationResults,
|
ValidationResults,
|
||||||
validateEntrypoints,
|
validateEntrypoints,
|
||||||
} from '../validation';
|
} from '../validation';
|
||||||
import consola from 'consola';
|
|
||||||
import { wxt } from '../../wxt';
|
import { wxt } from '../../wxt';
|
||||||
import { mergeJsonOutputs } from '@aklinker1/rollup-plugin-visualizer';
|
import { mergeJsonOutputs } from '@aklinker1/rollup-plugin-visualizer';
|
||||||
import { isCI } from 'ci-info';
|
import { isCI } from 'ci-info';
|
||||||
@@ -134,12 +133,12 @@ function printValidationResults({
|
|||||||
}, new Map<Entrypoint, ValidationResult[]>());
|
}, new Map<Entrypoint, ValidationResult[]>());
|
||||||
|
|
||||||
Array.from(entrypointErrors.entries()).forEach(([entrypoint, errors]) => {
|
Array.from(entrypointErrors.entries()).forEach(([entrypoint, errors]) => {
|
||||||
consola.log(relative(cwd, entrypoint.inputPath));
|
wxt.logger.log(relative(cwd, entrypoint.inputPath));
|
||||||
console.log();
|
console.log();
|
||||||
errors.forEach((err) => {
|
errors.forEach((err) => {
|
||||||
const type = err.type === 'error' ? pc.red('ERROR') : pc.yellow('WARN');
|
const type = err.type === 'error' ? pc.red('ERROR') : pc.yellow('WARN');
|
||||||
const recieved = pc.dim(`(recieved: ${JSON.stringify(err.value)})`);
|
const recieved = pc.dim(`(recieved: ${JSON.stringify(err.value)})`);
|
||||||
consola.log(` - ${type} ${err.message} ${recieved}`);
|
wxt.logger.log(` - ${type} ${err.message} ${recieved}`);
|
||||||
});
|
});
|
||||||
console.log();
|
console.log();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,16 +12,19 @@ import {
|
|||||||
WxtCommand,
|
WxtCommand,
|
||||||
WxtModule,
|
WxtModule,
|
||||||
WxtModuleWithMetadata,
|
WxtModuleWithMetadata,
|
||||||
|
ResolvedEslintrc,
|
||||||
|
Eslintrc,
|
||||||
} from '~/types';
|
} from '~/types';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { createFsCache } from '~/core/utils/cache';
|
import { createFsCache } from '~/core/utils/cache';
|
||||||
import consola, { LogLevels } from 'consola';
|
import consola, { LogLevels } from 'consola';
|
||||||
import defu from 'defu';
|
import defu from 'defu';
|
||||||
import { NullablyRequired } from '../types';
|
import { NullablyRequired } from '../types';
|
||||||
import { isModuleInstalled } from '../package';
|
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import { normalizePath } from '../paths';
|
import { normalizePath } from '../paths';
|
||||||
import glob from 'fast-glob';
|
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
|
* Given an inline config, discover the config file if necessary, merge the results, resolve any
|
||||||
@@ -127,15 +130,21 @@ export async function resolveConfig(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const modules = await resolveWxtModules(modulesDir, mergedConfig.modules);
|
const userModules = await resolveWxtUserModules(
|
||||||
const moduleOptions = modules.reduce<Record<string, any>>((map, module) => {
|
modulesDir,
|
||||||
if (module.configKey) {
|
mergedConfig.modules,
|
||||||
map[module.configKey] =
|
);
|
||||||
// @ts-expect-error
|
const moduleOptions = userModules.reduce<Record<string, any>>(
|
||||||
mergedConfig[module.configKey];
|
(map, module) => {
|
||||||
}
|
if (module.configKey) {
|
||||||
return map;
|
map[module.configKey] =
|
||||||
}, {});
|
// @ts-expect-error
|
||||||
|
mergedConfig[module.configKey];
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
browser,
|
browser,
|
||||||
@@ -146,7 +155,7 @@ export async function resolveConfig(
|
|||||||
filterEntrypoints,
|
filterEntrypoints,
|
||||||
env,
|
env,
|
||||||
fsCache: createFsCache(wxtDir),
|
fsCache: createFsCache(wxtDir),
|
||||||
imports: await getUnimportOptions(wxtDir, logger, mergedConfig),
|
imports: await getUnimportOptions(wxtDir, srcDir, logger, mergedConfig),
|
||||||
logger,
|
logger,
|
||||||
manifest: await resolveManifestConfig(env, mergedConfig.manifest),
|
manifest: await resolveManifestConfig(env, mergedConfig.manifest),
|
||||||
manifestVersion,
|
manifestVersion,
|
||||||
@@ -160,14 +169,14 @@ export async function resolveConfig(
|
|||||||
srcDir,
|
srcDir,
|
||||||
typesDir,
|
typesDir,
|
||||||
wxtDir,
|
wxtDir,
|
||||||
zip: resolveZipConfig(root, mergedConfig),
|
zip: resolveZipConfig(root, outBaseDir, mergedConfig),
|
||||||
transformManifest: mergedConfig.transformManifest,
|
transformManifest: mergedConfig.transformManifest,
|
||||||
analysis: resolveAnalysisConfig(root, mergedConfig),
|
analysis: resolveAnalysisConfig(root, mergedConfig),
|
||||||
userConfigMetadata: userConfigMetadata ?? {},
|
userConfigMetadata: userConfigMetadata ?? {},
|
||||||
alias,
|
alias,
|
||||||
experimental: defu(mergedConfig.experimental, {
|
experimental: defu(mergedConfig.experimental, {
|
||||||
includeBrowserPolyfill: true,
|
includeBrowserPolyfill: true,
|
||||||
viteRuntime: false,
|
entrypointImporter: 'jiti' as const,
|
||||||
}),
|
}),
|
||||||
dev: {
|
dev: {
|
||||||
server: devServerConfig,
|
server: devServerConfig,
|
||||||
@@ -175,7 +184,8 @@ export async function resolveConfig(
|
|||||||
},
|
},
|
||||||
hooks: mergedConfig.hooks ?? {},
|
hooks: mergedConfig.hooks ?? {},
|
||||||
vite: mergedConfig.vite ?? (() => ({})),
|
vite: mergedConfig.vite ?? (() => ({})),
|
||||||
modules,
|
builtinModules,
|
||||||
|
userModules,
|
||||||
plugins: [],
|
plugins: [],
|
||||||
...moduleOptions,
|
...moduleOptions,
|
||||||
};
|
};
|
||||||
@@ -233,6 +243,7 @@ async function mergeInlineConfig(
|
|||||||
|
|
||||||
function resolveZipConfig(
|
function resolveZipConfig(
|
||||||
root: string,
|
root: string,
|
||||||
|
outBaseDir: string,
|
||||||
mergedConfig: InlineConfig,
|
mergedConfig: InlineConfig,
|
||||||
): NullablyRequired<ResolvedConfig['zip']> {
|
): NullablyRequired<ResolvedConfig['zip']> {
|
||||||
const downloadedPackagesDir = path.resolve(root, '.wxt/local_modules');
|
const downloadedPackagesDir = path.resolve(root, '.wxt/local_modules');
|
||||||
@@ -253,6 +264,8 @@ function resolveZipConfig(
|
|||||||
// Tests
|
// Tests
|
||||||
'**/__tests__/**',
|
'**/__tests__/**',
|
||||||
'**/*.+(test|spec).?(c|m)+(j|t)s?(x)',
|
'**/*.+(test|spec).?(c|m)+(j|t)s?(x)',
|
||||||
|
// Output directory
|
||||||
|
`${path.relative(root, outBaseDir)}/**`,
|
||||||
// From user
|
// From user
|
||||||
...(mergedConfig.zip?.excludeSources ?? []),
|
...(mergedConfig.zip?.excludeSources ?? []),
|
||||||
],
|
],
|
||||||
@@ -285,22 +298,12 @@ function resolveAnalysisConfig(
|
|||||||
|
|
||||||
async function getUnimportOptions(
|
async function getUnimportOptions(
|
||||||
wxtDir: string,
|
wxtDir: string,
|
||||||
|
srcDir: string,
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
config: InlineConfig,
|
config: InlineConfig,
|
||||||
): Promise<WxtResolvedUnimportOptions | false> {
|
): Promise<WxtResolvedUnimportOptions | false> {
|
||||||
if (config.imports === false) return false;
|
if (config.imports === false) return false;
|
||||||
|
|
||||||
const enabledConfig = config.imports?.eslintrc?.enabled;
|
|
||||||
let enabled: boolean;
|
|
||||||
switch (enabledConfig) {
|
|
||||||
case undefined:
|
|
||||||
case 'auto':
|
|
||||||
enabled = await isModuleInstalled('eslint');
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
enabled = enabledConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultOptions: WxtResolvedUnimportOptions = {
|
const defaultOptions: WxtResolvedUnimportOptions = {
|
||||||
debugLog: logger.debug,
|
debugLog: logger.debug,
|
||||||
imports: [
|
imports: [
|
||||||
@@ -315,11 +318,10 @@ async function getUnimportOptions(
|
|||||||
],
|
],
|
||||||
warn: logger.warn,
|
warn: logger.warn,
|
||||||
dirs: ['components', 'composables', 'hooks', 'utils'],
|
dirs: ['components', 'composables', 'hooks', 'utils'],
|
||||||
eslintrc: {
|
dirsScanOptions: {
|
||||||
enabled,
|
cwd: srcDir,
|
||||||
filePath: path.resolve(wxtDir, 'eslintrc-auto-import.json'),
|
|
||||||
globalsPropValue: true,
|
|
||||||
},
|
},
|
||||||
|
eslintrc: await getUnimportEslintOptions(wxtDir, config.imports?.eslintrc),
|
||||||
};
|
};
|
||||||
|
|
||||||
return defu<WxtResolvedUnimportOptions, [WxtResolvedUnimportOptions]>(
|
return defu<WxtResolvedUnimportOptions, [WxtResolvedUnimportOptions]>(
|
||||||
@@ -328,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`.
|
* Returns the path to `node_modules/wxt`.
|
||||||
*/
|
*/
|
||||||
@@ -378,7 +414,7 @@ export async function mergeBuilderConfig(
|
|||||||
throw Error('Builder not found. Make sure vite is installed.');
|
throw Error('Builder not found. Make sure vite is installed.');
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function resolveWxtModules(
|
export async function resolveWxtUserModules(
|
||||||
modulesDir: string,
|
modulesDir: string,
|
||||||
modules: string[] = [],
|
modules: string[] = [],
|
||||||
): Promise<WxtModuleWithMetadata<any>[]> {
|
): Promise<WxtModuleWithMetadata<any>[]> {
|
||||||
|
|||||||
@@ -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 [];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,7 +20,3 @@ export async function getPackageJson(): Promise<
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isModuleInstalled(name: string) {
|
|
||||||
return import(/* @vite-ignore */ name).then(() => true).catch(() => false);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -239,12 +239,13 @@ export const fakeResolvedConfig = fakeObjectCreator<ResolvedConfig>(() => {
|
|||||||
command,
|
command,
|
||||||
entrypointsDir: fakeDir(),
|
entrypointsDir: fakeDir(),
|
||||||
modulesDir: fakeDir(),
|
modulesDir: fakeDir(),
|
||||||
modules: [],
|
builtinModules: [],
|
||||||
|
userModules: [],
|
||||||
env: { browser, command, manifestVersion, mode },
|
env: { browser, command, manifestVersion, mode },
|
||||||
fsCache: mock<FsCache>(),
|
fsCache: mock<FsCache>(),
|
||||||
imports: {
|
imports: {
|
||||||
eslintrc: {
|
eslintrc: {
|
||||||
enabled: faker.datatype.boolean(),
|
enabled: faker.helpers.arrayElement([false, 8, 9]),
|
||||||
filePath: fakeFile(),
|
filePath: fakeFile(),
|
||||||
globalsPropValue: faker.helpers.arrayElement([
|
globalsPropValue: faker.helpers.arrayElement([
|
||||||
true,
|
true,
|
||||||
@@ -297,7 +298,7 @@ export const fakeResolvedConfig = fakeObjectCreator<ResolvedConfig>(() => {
|
|||||||
alias: {},
|
alias: {},
|
||||||
experimental: {
|
experimental: {
|
||||||
includeBrowserPolyfill: true,
|
includeBrowserPolyfill: true,
|
||||||
viteRuntime: false,
|
entrypointImporter: 'jiti',
|
||||||
},
|
},
|
||||||
dev: {
|
dev: {
|
||||||
reloadCommand: 'Alt+R',
|
reloadCommand: 'Alt+R',
|
||||||
|
|||||||
@@ -5,11 +5,13 @@ import {
|
|||||||
WxtCommand,
|
WxtCommand,
|
||||||
WxtDevServer,
|
WxtDevServer,
|
||||||
WxtHooks,
|
WxtHooks,
|
||||||
|
WxtModule,
|
||||||
} from '~/types';
|
} from '~/types';
|
||||||
import { resolveConfig } from './utils/building';
|
import { resolveConfig } from './utils/building';
|
||||||
import { createHooks } from 'hookable';
|
import { createHooks } from 'hookable';
|
||||||
import { createWxtPackageManager } from './package-managers';
|
import { createWxtPackageManager } from './package-managers';
|
||||||
import { createViteBuilder } from './builders/vite';
|
import { createViteBuilder } from './builders/vite';
|
||||||
|
import { builtinModules } from '~/builtin-modules';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Global variable set once `createWxt` is called once. Since this variable is used everywhere, this
|
* Global variable set once `createWxt` is called once. Since this variable is used everywhere, this
|
||||||
@@ -46,18 +48,16 @@ export async function registerWxt(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Initialize modules
|
// Initialize modules
|
||||||
for (const module of config.modules) {
|
const initModule = async (module: WxtModule<any>) => {
|
||||||
if (module.hooks) wxt.hooks.addHooks(module.hooks);
|
if (module.hooks) wxt.hooks.addHooks(module.hooks);
|
||||||
if (wxt.config.imports !== false && module.imports) {
|
|
||||||
wxt.config.imports.imports ??= [];
|
|
||||||
wxt.config.imports.imports.push(...module.imports);
|
|
||||||
}
|
|
||||||
await module.setup?.(
|
await module.setup?.(
|
||||||
wxt,
|
wxt,
|
||||||
// @ts-expect-error: Untyped configKey field
|
// @ts-expect-error: Untyped configKey field
|
||||||
module.configKey ? config[module.configKey] : undefined,
|
module.configKey ? config[module.configKey] : undefined,
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
for (const builtinModule of builtinModules) await initModule(builtinModule);
|
||||||
|
for (const userModule of config.userModules) await initModule(userModule);
|
||||||
|
|
||||||
// Initialize hooks
|
// Initialize hooks
|
||||||
wxt.hooks.addHooks(config.hooks);
|
wxt.hooks.addHooks(config.hooks);
|
||||||
|
|||||||
@@ -104,7 +104,8 @@ async function zipDir(
|
|||||||
// Ignore node_modules, otherwise this glob step takes forever
|
// Ignore node_modules, otherwise this glob step takes forever
|
||||||
ignore: ['**/node_modules'],
|
ignore: ['**/node_modules'],
|
||||||
onlyFiles: true,
|
onlyFiles: true,
|
||||||
dot: true,
|
// TODO: Fix #738
|
||||||
|
// dot: true,
|
||||||
})
|
})
|
||||||
).filter((relativePath) => {
|
).filter((relativePath) => {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -104,13 +104,11 @@ export function addViteConfig(
|
|||||||
): void {
|
): void {
|
||||||
wxt.hooks.hook('ready', (wxt) => {
|
wxt.hooks.hook('ready', (wxt) => {
|
||||||
const userVite = wxt.config.vite;
|
const userVite = wxt.config.vite;
|
||||||
wxt.config.vite = (env) =>
|
wxt.config.vite = async (env) => {
|
||||||
vite.mergeConfig(
|
const fromUser = await userVite(env);
|
||||||
// Use config added by module as base
|
const fromModule = viteConfig(env) ?? {};
|
||||||
viteConfig(env) ?? {},
|
return vite.mergeConfig(fromModule, fromUser);
|
||||||
// Overwrite module config with user config
|
};
|
||||||
userVite(env),
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import { logger } from './utils/logger';
|
||||||
|
|
||||||
|
interface WebSocketMessage {
|
||||||
|
type: string;
|
||||||
|
event: string;
|
||||||
|
data?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WxtWebSocket extends WebSocket {
|
||||||
|
addWxtEventListener(
|
||||||
|
type: 'wxt:reload-extension',
|
||||||
|
callback: (event: CustomEvent<undefined>) => void,
|
||||||
|
options?: AddEventListenerOptions | boolean,
|
||||||
|
): void;
|
||||||
|
addWxtEventListener(
|
||||||
|
type: 'wxt:reload-content-script',
|
||||||
|
callback?: (event: CustomEvent<ReloadContentScriptPayload>) => void,
|
||||||
|
options?: AddEventListenerOptions | boolean,
|
||||||
|
): void;
|
||||||
|
addWxtEventListener(
|
||||||
|
type: 'wxt:reload-page',
|
||||||
|
callback?: (event: CustomEvent<string>) => void,
|
||||||
|
options?: AddEventListenerOptions | boolean,
|
||||||
|
): void;
|
||||||
|
sendCustom(event: string, payload?: any): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
let ws: WxtWebSocket | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connect to the websocket and listen for messages.
|
||||||
|
*
|
||||||
|
* @param onMessage Optional callback that is called when a message is recieved and we've verified
|
||||||
|
* it's structure is what we expect.
|
||||||
|
*/
|
||||||
|
export function getDevServerWebSocket(): WxtWebSocket {
|
||||||
|
if (import.meta.env.COMMAND !== 'serve')
|
||||||
|
throw Error(
|
||||||
|
'Must be running WXT dev command to connect to call getDevServerWebSocket()',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (ws == null) {
|
||||||
|
const serverUrl = `${__DEV_SERVER_PROTOCOL__}//${__DEV_SERVER_HOSTNAME__}:${__DEV_SERVER_PORT__}`;
|
||||||
|
logger.debug('Connecting to dev server @', serverUrl);
|
||||||
|
|
||||||
|
ws = new WebSocket(serverUrl, 'vite-hmr') as WxtWebSocket;
|
||||||
|
ws.addWxtEventListener = ws.addEventListener.bind(ws);
|
||||||
|
ws.sendCustom = (event, payload) =>
|
||||||
|
ws?.send(JSON.stringify({ type: 'custom', event, payload }));
|
||||||
|
|
||||||
|
ws.addEventListener('open', () => {
|
||||||
|
logger.debug('Connected to dev server');
|
||||||
|
});
|
||||||
|
ws.addEventListener('close', () => {
|
||||||
|
logger.debug('Disconnected from dev server');
|
||||||
|
});
|
||||||
|
ws.addEventListener('error', (event) => {
|
||||||
|
logger.error('Failed to connect to dev server', event);
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.addEventListener('message', (e) => {
|
||||||
|
try {
|
||||||
|
const message = JSON.parse(e.data) as WebSocketMessage;
|
||||||
|
if (message.type === 'custom') {
|
||||||
|
ws?.dispatchEvent(
|
||||||
|
new CustomEvent(message.event, { detail: message.data }),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
logger.error('Failed to handle message', err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return ws;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ReloadContentScriptPayload {
|
||||||
|
registration?: 'manifest' | 'runtime';
|
||||||
|
contentScript: {
|
||||||
|
matches: string[];
|
||||||
|
js?: string[];
|
||||||
|
css?: string[];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import type * as vite from 'vite';
|
import type * as vite from 'vite';
|
||||||
import {
|
import {
|
||||||
unimport,
|
|
||||||
download,
|
download,
|
||||||
tsconfigPaths,
|
tsconfigPaths,
|
||||||
globals,
|
globals,
|
||||||
@@ -8,6 +7,8 @@ import {
|
|||||||
} from '../core/builders/vite/plugins';
|
} from '../core/builders/vite/plugins';
|
||||||
import { resolveConfig } from '~/core/utils/building';
|
import { resolveConfig } from '~/core/utils/building';
|
||||||
import { InlineConfig } from '../types';
|
import { InlineConfig } from '../types';
|
||||||
|
import { vitePlugin as unimportPlugin } from '~/builtin-modules/unimport';
|
||||||
|
import { createUnimport } from 'unimport';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Vite plugin that configures Vitest with everything required to test a WXT extension, based on the `<root>/wxt.config.ts`
|
* Vite plugin that configures Vitest with everything required to test a WXT extension, based on the `<root>/wxt.config.ts`
|
||||||
@@ -25,11 +26,18 @@ import { InlineConfig } from '../types';
|
|||||||
* @param inlineConfig Customize WXT's config for testing. Any config specified here overrides the config from your `wxt.config.ts` file.
|
* @param inlineConfig Customize WXT's config for testing. Any config specified here overrides the config from your `wxt.config.ts` file.
|
||||||
*/
|
*/
|
||||||
export function WxtVitest(inlineConfig?: InlineConfig): vite.PluginOption {
|
export function WxtVitest(inlineConfig?: InlineConfig): vite.PluginOption {
|
||||||
return resolveConfig(inlineConfig ?? {}, 'serve').then((config) => [
|
return resolveConfig(inlineConfig ?? {}, 'serve').then(async (config) => {
|
||||||
webextensionPolyfillMock(config),
|
const plugins = [
|
||||||
unimport(config),
|
webextensionPolyfillMock(config),
|
||||||
globals(config),
|
globals(config),
|
||||||
download(config),
|
download(config),
|
||||||
tsconfigPaths(config),
|
tsconfigPaths(config),
|
||||||
]);
|
];
|
||||||
|
if (config.imports !== false) {
|
||||||
|
const unimport = createUnimport(config.imports);
|
||||||
|
await unimport.init();
|
||||||
|
plugins.push(unimportPlugin(unimport));
|
||||||
|
}
|
||||||
|
return plugins;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -317,14 +317,17 @@ export interface InlineConfig {
|
|||||||
*/
|
*/
|
||||||
includeBrowserPolyfill?: boolean;
|
includeBrowserPolyfill?: boolean;
|
||||||
/**
|
/**
|
||||||
* When set to `true`, use the Vite Runtime API to load entrypoint options instead of the default, `jiti`.
|
* Method used to import entrypoint files during the build process to extract their options.
|
||||||
*
|
*
|
||||||
* Lets you use imported variables and leverage your Vite config to add support for non-standard APIs/syntax.
|
* - "jiti": Simplest and fastest, but doesn't allow using any imported variables outside the entrypoint's main function
|
||||||
|
* - "vite-runtime" (unstable): Uses Vite 5.3's new runtime API to import the entrypoints. Automatically includes vite config based on your wxt.config.ts file
|
||||||
|
* - "vite-node" (unstable): Uses `vite-node` to import the entrypoints. Automatically includes vite config based on your wxt.config.ts file
|
||||||
*
|
*
|
||||||
* @experimental Early access to try out the feature before it becomes the default.
|
* @see {@link https://wxt.dev/guide/go-further/entrypoint-side-effects.html|Entrypoint Side-effect Docs}
|
||||||
* @default false
|
*
|
||||||
|
* @default "jiti"
|
||||||
*/
|
*/
|
||||||
viteRuntime?: boolean;
|
entrypointImporter?: 'jiti' | 'vite-runtime' | 'vite-node';
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* Config effecting dev mode only.
|
* Config effecting dev mode only.
|
||||||
@@ -1031,6 +1034,23 @@ export interface WxtHooks {
|
|||||||
* @returns Promise
|
* @returns Promise
|
||||||
*/
|
*/
|
||||||
ready: (wxt: Wxt) => HookResult;
|
ready: (wxt: Wxt) => HookResult;
|
||||||
|
/**
|
||||||
|
* Called before WXT writes .wxt/tsconfig.json and .wxt/wxt.d.ts, allowing
|
||||||
|
* addition of custom references and declarations in wxt.d.ts, or directly
|
||||||
|
* modifying the options in `tsconfig.json`.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* wxt.hooks.hook("prepare:types", (wxt, entries) => {
|
||||||
|
* // Add a file, ".wxt/types/example.d.ts", that defines a global
|
||||||
|
* // variable called "example" in the TS project.
|
||||||
|
* entries.push({
|
||||||
|
* path: "types/example.d.ts",
|
||||||
|
* textContent: "declare const a: string;",
|
||||||
|
* tsReference: true,
|
||||||
|
* });
|
||||||
|
* })
|
||||||
|
*/
|
||||||
|
'prepare:types': (wxt: Wxt, entries: WxtDirEntry[]) => HookResult;
|
||||||
/**
|
/**
|
||||||
* Called before the build is started in both dev mode and build mode.
|
* Called before the build is started in both dev mode and build mode.
|
||||||
*
|
*
|
||||||
@@ -1174,7 +1194,7 @@ export interface ResolvedConfig {
|
|||||||
alias: Record<string, string>;
|
alias: Record<string, string>;
|
||||||
experimental: {
|
experimental: {
|
||||||
includeBrowserPolyfill: boolean;
|
includeBrowserPolyfill: boolean;
|
||||||
viteRuntime: boolean;
|
entrypointImporter: 'jiti' | 'vite-runtime' | 'vite-node';
|
||||||
};
|
};
|
||||||
dev: {
|
dev: {
|
||||||
/** Only defined during dev command */
|
/** Only defined during dev command */
|
||||||
@@ -1185,7 +1205,8 @@ export interface ResolvedConfig {
|
|||||||
reloadCommand: string | false;
|
reloadCommand: string | false;
|
||||||
};
|
};
|
||||||
hooks: NestedHooks<WxtHooks>;
|
hooks: NestedHooks<WxtHooks>;
|
||||||
modules: WxtModuleWithMetadata<any>[];
|
builtinModules: WxtModule<any>[];
|
||||||
|
userModules: WxtModuleWithMetadata<any>[];
|
||||||
/**
|
/**
|
||||||
* An array of string to import plugins from. These paths should be
|
* An array of string to import plugins from. These paths should be
|
||||||
* resolvable by vite, and they should `export default defineWxtPlugin(...)`.
|
* resolvable by vite, and they should `export default defineWxtPlugin(...)`.
|
||||||
@@ -1217,17 +1238,21 @@ export interface Eslintrc {
|
|||||||
/**
|
/**
|
||||||
* When true, generates a file that can be used by ESLint to know which variables are valid globals.
|
* 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
|
* - `false`: Don't generate the file.
|
||||||
* - `true`: Generate the helper file
|
* - `'auto'`: Check if eslint is installed, and if it is, generate a compatible config file.
|
||||||
* - `false`: Don't generate the 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'
|
* @default 'auto'
|
||||||
*/
|
*/
|
||||||
enabled?: boolean | 'auto';
|
enabled?: false | true | 'auto' | 8 | 9;
|
||||||
/**
|
/**
|
||||||
* File path to save the generated eslint config.
|
* 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;
|
filePath?: string;
|
||||||
/**
|
/**
|
||||||
@@ -1237,7 +1262,8 @@ export interface Eslintrc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ResolvedEslintrc {
|
export interface ResolvedEslintrc {
|
||||||
enabled: boolean;
|
/** False if disabled, otherwise the major version of ESLint installed */
|
||||||
|
enabled: false | 8 | 9;
|
||||||
/** Absolute path */
|
/** Absolute path */
|
||||||
filePath: string;
|
filePath: string;
|
||||||
globalsPropValue: EslintGlobalsPropValue;
|
globalsPropValue: EslintGlobalsPropValue;
|
||||||
@@ -1331,13 +1357,9 @@ export interface WxtModuleWithMetadata<TOptions extends WxtModuleOptions>
|
|||||||
id: string;
|
id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ResolvedPublicFile {
|
export type ResolvedPublicFile = CopiedPublicFile | GeneratedPublicFile;
|
||||||
/**
|
|
||||||
* The absolute path to the file that will be copied to the output directory.
|
export interface ResolvedBasePublicFile {
|
||||||
* @example
|
|
||||||
* "/path/to/any/file.css"
|
|
||||||
*/
|
|
||||||
absoluteSrc: string;
|
|
||||||
/**
|
/**
|
||||||
* The relative path in the output directory to copy the file to.
|
* The relative path in the output directory to copy the file to.
|
||||||
* @example
|
* @example
|
||||||
@@ -1346,4 +1368,47 @@ export interface ResolvedPublicFile {
|
|||||||
relativeDest: string;
|
relativeDest: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CopiedPublicFile extends ResolvedBasePublicFile {
|
||||||
|
/**
|
||||||
|
* The absolute path to the file that will be copied to the output directory.
|
||||||
|
* @example
|
||||||
|
* "/path/to/any/file.css"
|
||||||
|
*/
|
||||||
|
absoluteSrc: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GeneratedPublicFile extends ResolvedBasePublicFile {
|
||||||
|
/**
|
||||||
|
* Text to write to the file.
|
||||||
|
*/
|
||||||
|
contents: string;
|
||||||
|
}
|
||||||
|
|
||||||
export type WxtPlugin = () => void;
|
export type WxtPlugin = () => void;
|
||||||
|
|
||||||
|
export type WxtDirEntry = WxtDirTypeReferenceEntry | WxtDirFileEntry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents type reference to a node module to be added to `.wxt/wxt.d.ts` file
|
||||||
|
*/
|
||||||
|
export interface WxtDirTypeReferenceEntry {
|
||||||
|
module: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a file to be written to the project's `.wxt/` directory.
|
||||||
|
*/
|
||||||
|
export interface WxtDirFileEntry {
|
||||||
|
/**
|
||||||
|
* Path relative to the `.wxt/` directory. So "tsconfig.json" would resolve to ".wxt/tsconfig.json".
|
||||||
|
*/
|
||||||
|
path: string;
|
||||||
|
/**
|
||||||
|
* The text that will be written to the file.
|
||||||
|
*/
|
||||||
|
text: string;
|
||||||
|
/**
|
||||||
|
* Set to `true` to add a reference to this file in `.wxt/wxt.d.ts`.
|
||||||
|
*/
|
||||||
|
tsReference?: boolean;
|
||||||
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user