Get Tailwind HMR working (first cut) (#1736)
* Get Tailwind HMR working * PR feedback * perf: improve HMR `head` performance Co-authored-by: Nate Moore <nate@skypack.dev>
This commit is contained in:
parent
2c5e67bb44
commit
2e1bded735
13 changed files with 175 additions and 84 deletions
5
.changeset/shaggy-guests-type.md
Normal file
5
.changeset/shaggy-guests-type.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'astro': patch
|
||||
---
|
||||
|
||||
fix: Improve Tailwind HMR
|
|
@ -235,6 +235,14 @@ Now you're ready to write Tailwind! Our recommended approach is to create a `src
|
|||
@tailwind utilities;
|
||||
```
|
||||
|
||||
Lastly, add it to your Astro page (or layout template):
|
||||
|
||||
```astro
|
||||
<head>
|
||||
<link rel="stylesheet" href={Astro.resolve('../styles/global.css')}>
|
||||
</head>
|
||||
```
|
||||
|
||||
As an alternative to `src/styles/global.css`, You may also add Tailwind utilities to individual `pages/*.astro` components in `<style>` tags, but be mindful of duplication! If you end up creating multiple Tailwind-managed stylesheets for your site, make sure you're not sending the same CSS to users over and over again in separate CSS files.
|
||||
|
||||
#### Migrating from v0.19
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"astro": "^0.21.0-next.1",
|
||||
"autoprefixer": "^10.3.7",
|
||||
"tailwindcss": "^2.2.17"
|
||||
"autoprefixer": "^10.4.0",
|
||||
"tailwindcss": "^2.2.19"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
tailwindcss: {
|
||||
config: path.join(__dirname, 'tailwind.config.js'), // update this if your path differs!
|
||||
},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
<button class="py-2 px-4 bg-green-500 text-white font-semibold rounded-lg shadow-md hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-400 focus:ring-opacity-75">
|
||||
---
|
||||
let { type = 'button' } = Astro.props;
|
||||
---
|
||||
<button class="py-2 px-4 bg-purple-500 text-white font-semibold rounded-lg shadow-md hover:bg-purple-700 focus:outline-none focus:ring-2 focus:ring-purple-400 focus:ring-opacity-75" type={type}>
|
||||
<slot />
|
||||
</button>
|
||||
|
|
|
@ -8,9 +8,7 @@ import Button from '../components/Button.astro';
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||
<title>Astro + TailwindCSS</title>
|
||||
<link rel="stylesheet" type="text/css" href={Astro.resolve("../styles/global.css")}>
|
||||
</head>
|
||||
|
|
|
@ -139,8 +139,11 @@ export interface CollectionRSS {
|
|||
|
||||
/** Generic interface for a component (Astro, Svelte, React, etc.) */
|
||||
export interface ComponentInstance {
|
||||
$$metadata: {
|
||||
modules: { module: Record<string, unknown>; specifier: string }[];
|
||||
fileURL: URL;
|
||||
};
|
||||
default: AstroComponentFactory;
|
||||
css?: string[];
|
||||
getStaticPaths?: (options: GetStaticPathsOptions) => GetStaticPathsResult;
|
||||
}
|
||||
|
||||
|
@ -263,6 +266,9 @@ export interface Renderer {
|
|||
knownEntrypoints?: string[];
|
||||
}
|
||||
|
||||
/** <link> tags with attributes represented by an object */
|
||||
export type Resource = Record<string, string>;
|
||||
|
||||
export interface RouteData {
|
||||
component: string;
|
||||
generate: (data?: any) => string;
|
||||
|
|
|
@ -4,6 +4,7 @@ import type { AstroConfig, ManifestData, RouteCache, RouteData } from '../../@ty
|
|||
import type { LogOptions } from '../logger';
|
||||
import type { HmrContext, ModuleNode } from '../vite';
|
||||
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { promisify } from 'util';
|
||||
import connect from 'connect';
|
||||
|
@ -13,6 +14,8 @@ import stripAnsi from 'strip-ansi';
|
|||
import vite from '../vite.js';
|
||||
import { defaultLogOptions, error, info } from '../logger.js';
|
||||
import { ssr } from '../ssr/index.js';
|
||||
import { STYLE_EXTENSIONS } from '../ssr/css.js';
|
||||
import { collectResources } from '../ssr/html.js';
|
||||
import { createRouteManifest, matchRoute } from '../ssr/routing.js';
|
||||
import { createVite } from '../create-vite.js';
|
||||
import * as msg from './messages.js';
|
||||
|
@ -51,7 +54,6 @@ export class AstroDevServer {
|
|||
httpServer: http.Server | undefined;
|
||||
hostname: string;
|
||||
port: number;
|
||||
|
||||
private config: AstroConfig;
|
||||
private logging: LogOptions;
|
||||
private manifest: ManifestData;
|
||||
|
@ -92,49 +94,94 @@ export class AstroDevServer {
|
|||
}
|
||||
|
||||
public async handleHotUpdate({ file, modules }: HmrContext): Promise<void | ModuleNode[]> {
|
||||
if (!this.viteServer) throw new Error(`AstroDevServer.start() not called`);
|
||||
const { viteServer } = this;
|
||||
if (!viteServer) throw new Error(`AstroDevServer.start() not called`);
|
||||
|
||||
for (const module of modules) {
|
||||
this.viteServer.moduleGraph.invalidateModule(module);
|
||||
viteServer.moduleGraph.invalidateModule(module);
|
||||
}
|
||||
|
||||
const route = this.mostRecentRoute;
|
||||
const pathname = route?.pathname ?? '/';
|
||||
|
||||
if (!route) {
|
||||
this.viteServer.ws.send({
|
||||
viteServer.ws.send({
|
||||
type: 'full-reload',
|
||||
});
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
const filePath = new URL(`./${route.component}`, this.config.projectRoot);
|
||||
// try to update the most recent route
|
||||
const html = await ssr({
|
||||
astroConfig: this.config,
|
||||
filePath: new URL(`./${route.component}`, this.config.projectRoot),
|
||||
filePath,
|
||||
logging: this.logging,
|
||||
mode: 'development',
|
||||
origin: this.origin,
|
||||
pathname,
|
||||
route,
|
||||
routeCache: this.routeCache,
|
||||
viteServer: this.viteServer,
|
||||
viteServer,
|
||||
});
|
||||
|
||||
// collect style tags to be reloaded (needed for Tailwind HMR, etc.)
|
||||
let invalidatedModules: vite.ModuleNode[] = [];
|
||||
await Promise.all(
|
||||
collectResources(html)
|
||||
.filter(({ href }) => {
|
||||
if (!href) return false;
|
||||
const ext = path.extname(href);
|
||||
return STYLE_EXTENSIONS.has(ext);
|
||||
})
|
||||
.map(async ({ href }) => {
|
||||
const viteModule =
|
||||
viteServer.moduleGraph.getModuleById(`${href}?direct`) ||
|
||||
(await viteServer.moduleGraph.getModuleByUrl(`${href}?direct`)) ||
|
||||
viteServer.moduleGraph.getModuleById(href) ||
|
||||
(await viteServer.moduleGraph.getModuleByUrl(href));
|
||||
if (viteModule) {
|
||||
invalidatedModules.push(viteModule);
|
||||
viteServer.moduleGraph.invalidateModule(viteModule);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// TODO: log update
|
||||
this.viteServer.ws.send({
|
||||
viteServer.ws.send({
|
||||
type: 'custom',
|
||||
event: 'astro:reload',
|
||||
data: { html },
|
||||
});
|
||||
|
||||
for (const viteModule of invalidatedModules) {
|
||||
// Note: from the time viteServer.moduleGraph.invalidateModule() is called above until now, CSS
|
||||
// is building in the background. For things like Tailwind, this can take some time. If the
|
||||
// CSS is still processing by the time HMR fires, we’ll end up with stale styles on the page.
|
||||
// TODO: fix this hack by figuring out how to add these styles to the { modules } above
|
||||
setTimeout(() => {
|
||||
viteServer.ws.send({
|
||||
type: 'update',
|
||||
updates: [
|
||||
{
|
||||
type: viteModule.type === 'js' ? 'js-update' : 'css-update',
|
||||
path: viteModule.id || viteModule.file || viteModule.url,
|
||||
acceptedPath: viteModule.url,
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
],
|
||||
});
|
||||
}, 150);
|
||||
}
|
||||
|
||||
return [];
|
||||
} catch (e) {
|
||||
const err = e as Error;
|
||||
this.viteServer.ssrFixStacktrace(err);
|
||||
viteServer.ssrFixStacktrace(err);
|
||||
// eslint-disable-next-line
|
||||
console.error(err.stack);
|
||||
this.viteServer.ws.send({
|
||||
viteServer.ws.send({
|
||||
type: 'full-reload',
|
||||
});
|
||||
return [];
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import type vite from '../../../vendor/vite';
|
||||
|
||||
import path from 'path';
|
||||
import htmlparser2 from 'htmlparser2';
|
||||
|
||||
// https://vitejs.dev/guide/features.html#css-pre-processors
|
||||
export const STYLE_EXTENSIONS = new Set(['.css', '.pcss', '.scss', '.sass', '.styl', '.stylus', '.less']);
|
||||
|
|
|
@ -4,53 +4,68 @@ import htmlparser2 from 'htmlparser2';
|
|||
|
||||
/** Inject tags into HTML (note: for best performance, group as many tags as possible into as few calls as you can) */
|
||||
export function injectTags(html: string, tags: vite.HtmlTagDescriptor[]): string {
|
||||
// TODO: this usually takes 5ms or less, but if it becomes a bottleneck we can create a WeakMap cache
|
||||
let output = html;
|
||||
if (!tags.length) return output;
|
||||
|
||||
const pos = { 'head-prepend': -1, head: -1, 'body-prepend': -1, body: -1 };
|
||||
|
||||
try {
|
||||
// parse html
|
||||
const parser = new htmlparser2.Parser({
|
||||
onopentag(tagname) {
|
||||
if (tagname === 'head') pos['head-prepend'] = parser.endIndex + 1;
|
||||
if (tagname === 'body') pos['body-prepend'] = parser.endIndex + 1;
|
||||
},
|
||||
onclosetag(tagname) {
|
||||
if (tagname === 'head') pos['head'] = parser.startIndex;
|
||||
if (tagname === 'body') pos['body'] = parser.startIndex;
|
||||
},
|
||||
});
|
||||
parser.write(html);
|
||||
parser.end();
|
||||
// parse html
|
||||
const parser = new htmlparser2.Parser({
|
||||
onopentag(tagname) {
|
||||
if (tagname === 'head') pos['head-prepend'] = parser.endIndex + 1;
|
||||
if (tagname === 'body') pos['body-prepend'] = parser.endIndex + 1;
|
||||
},
|
||||
onclosetag(tagname) {
|
||||
if (tagname === 'head') pos['head'] = parser.startIndex;
|
||||
if (tagname === 'body') pos['body'] = parser.startIndex;
|
||||
},
|
||||
});
|
||||
parser.write(html);
|
||||
parser.end();
|
||||
|
||||
// inject
|
||||
const lastToFirst = Object.entries(pos).sort((a, b) => b[1] - a[1]);
|
||||
lastToFirst.forEach(([name, i]) => {
|
||||
if (i === -1) {
|
||||
// TODO: warn on missing tag? Is this an HTML partial?
|
||||
return;
|
||||
// inject
|
||||
const lastToFirst = Object.entries(pos).sort((a, b) => b[1] - a[1]);
|
||||
lastToFirst.forEach(([name, i]) => {
|
||||
if (i === -1) {
|
||||
// TODO: warn on missing tag? Is this an HTML partial?
|
||||
return;
|
||||
}
|
||||
let selected = tags.filter(({ injectTo }) => {
|
||||
if (name === 'head-prepend' && !injectTo) {
|
||||
return true; // "head-prepend" is the default
|
||||
} else {
|
||||
return injectTo === name;
|
||||
}
|
||||
let selected = tags.filter(({ injectTo }) => {
|
||||
if (name === 'head-prepend' && !injectTo) {
|
||||
return true; // "head-prepend" is the default
|
||||
} else {
|
||||
return injectTo === name;
|
||||
}
|
||||
});
|
||||
if (!selected.length) return;
|
||||
output = output.substring(0, i) + serializeTags(selected) + html.substring(i);
|
||||
});
|
||||
} catch (err) {
|
||||
// on invalid HTML, do nothing
|
||||
}
|
||||
if (!selected.length) return;
|
||||
output = output.substring(0, i) + serializeTags(selected) + html.substring(i);
|
||||
});
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
// Everything below © Vite
|
||||
type Resource = Record<string, string>;
|
||||
|
||||
/** Collect resources (scans final, rendered HTML so expressions have been applied) */
|
||||
export function collectResources(html: string): Resource[] {
|
||||
let resources: Resource[] = [];
|
||||
const parser = new htmlparser2.Parser({
|
||||
// <link> tags are self-closing, so only use onopentag (avoid onattribute or onclosetag)
|
||||
onopentag(tagname, attrs) {
|
||||
if (tagname === 'link') resources.push(attrs);
|
||||
},
|
||||
});
|
||||
parser.write(html);
|
||||
parser.end();
|
||||
return resources;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------
|
||||
// Everything below © Vite. Rather than invent our own tag creating API, we borrow
|
||||
// Vite’s `transformIndexHtml()` API for ease-of-use and consistency. But we need
|
||||
// to borrow a few private methods in Vite to make that available here.
|
||||
// https://github.com/vitejs/vite/blob/main/packages/vite/src/node/plugins/html.ts
|
||||
// -------------------------------------------------------------------------------
|
||||
|
||||
// Vite is released under the MIT license:
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ import type { AstroConfig, ComponentInstance, GetStaticPathsResult, Params, Prop
|
|||
import type { AstroGlobal, TopLevelAstro, SSRResult, SSRElement } from '../../@types/astro-runtime';
|
||||
import type { LogOptions } from '../logger';
|
||||
|
||||
import { fileURLToPath } from 'url';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { renderPage, renderSlot } from '../../runtime/server/index.js';
|
||||
|
@ -164,7 +163,7 @@ export async function ssr({ astroConfig, filePath, logging, mode, origin, pathna
|
|||
// inject tags
|
||||
const tags: vite.HtmlTagDescriptor[] = [];
|
||||
|
||||
// inject Astro HMR client (dev only)
|
||||
// dev only: inject Astro HMR client
|
||||
if (mode === 'development') {
|
||||
tags.push({
|
||||
tag: 'script',
|
||||
|
|
|
@ -6,10 +6,17 @@ if (import.meta.hot) {
|
|||
|
||||
morphdom(document.head, doc.head, {
|
||||
onBeforeElUpdated(fromEl, toEl) {
|
||||
// Do not update identical tags
|
||||
if (fromEl.isEqualNode(toEl)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Do not update <link> or <script> tags
|
||||
// to avoid re-fetching their contents
|
||||
if (fromEl.tagName === toEl.tagName && (toEl.tagName === 'LINK' || toEl.tagName === 'SCRIPT')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
});
|
||||
|
|
60
yarn.lock
60
yarn.lock
|
@ -2549,16 +2549,16 @@ at-least-node@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2"
|
||||
integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==
|
||||
|
||||
autoprefixer@^10.3.7:
|
||||
version "10.3.7"
|
||||
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.3.7.tgz#cef2562058406bd378c94aacda36bb46a97b3186"
|
||||
integrity sha512-EmGpu0nnQVmMhX8ROoJ7Mx8mKYPlcUHuxkwrRYEYMz85lu7H09v8w6R1P0JPdn/hKU32GjpLBFEOuIlDWCRWvg==
|
||||
autoprefixer@^10.4.0:
|
||||
version "10.4.0"
|
||||
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.0.tgz#c3577eb32a1079a440ec253e404eaf1eb21388c8"
|
||||
integrity sha512-7FdJ1ONtwzV1G43GDD0kpVMn/qbiNqyOPMFTX5nRffI+7vgWoFEc6DcXOxHJxrWNDXrZh18eDsZjvZGUljSRGA==
|
||||
dependencies:
|
||||
browserslist "^4.17.3"
|
||||
caniuse-lite "^1.0.30001264"
|
||||
browserslist "^4.17.5"
|
||||
caniuse-lite "^1.0.30001272"
|
||||
fraction.js "^4.1.1"
|
||||
normalize-range "^0.1.2"
|
||||
picocolors "^0.2.1"
|
||||
picocolors "^1.0.0"
|
||||
postcss-value-parser "^4.1.0"
|
||||
|
||||
available-typed-arrays@^1.0.5:
|
||||
|
@ -2778,15 +2778,15 @@ browserslist@^4.16.6:
|
|||
escalade "^3.1.1"
|
||||
node-releases "^1.1.75"
|
||||
|
||||
browserslist@^4.17.3:
|
||||
version "4.17.4"
|
||||
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.17.4.tgz#72e2508af2a403aec0a49847ef31bd823c57ead4"
|
||||
integrity sha512-Zg7RpbZpIJRW3am9Lyckue7PLytvVxxhJj1CaJVlCWENsGEAOlnlt8X0ZxGRPp7Bt9o8tIRM5SEXy4BCPMJjLQ==
|
||||
browserslist@^4.17.5:
|
||||
version "4.17.5"
|
||||
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.17.5.tgz#c827bbe172a4c22b123f5e337533ceebadfdd559"
|
||||
integrity sha512-I3ekeB92mmpctWBoLXe0d5wPS2cBuRvvW0JyyJHMrk9/HmP2ZjrTboNAZ8iuGqaEIlKguljbQY32OkOJIRrgoA==
|
||||
dependencies:
|
||||
caniuse-lite "^1.0.30001265"
|
||||
electron-to-chromium "^1.3.867"
|
||||
caniuse-lite "^1.0.30001271"
|
||||
electron-to-chromium "^1.3.878"
|
||||
escalade "^3.1.1"
|
||||
node-releases "^2.0.0"
|
||||
node-releases "^2.0.1"
|
||||
picocolors "^1.0.0"
|
||||
|
||||
buffer-crc32@~0.2.3:
|
||||
|
@ -2933,10 +2933,10 @@ caniuse-lite@^1.0.30001254:
|
|||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001258.tgz#b604eed80cc54a578e4bf5a02ae3ed49f869d252"
|
||||
integrity sha512-RBByOG6xWXUp0CR2/WU2amXz3stjKpSl5J1xU49F1n2OxD//uBZO4wCKUiG+QMGf7CHGfDDcqoKriomoGVxTeA==
|
||||
|
||||
caniuse-lite@^1.0.30001264, caniuse-lite@^1.0.30001265:
|
||||
version "1.0.30001267"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001267.tgz#b1cf2937175afc0570e4615fc2d2f9069fa0ed30"
|
||||
integrity sha512-r1mjTzAuJ9W8cPBGbbus8E0SKcUP7gn03R14Wk8FlAlqhH9hroy9nLqmpuXlfKEw/oILW+FGz47ipXV2O7x8lg==
|
||||
caniuse-lite@^1.0.30001271, caniuse-lite@^1.0.30001272:
|
||||
version "1.0.30001274"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001274.tgz#26ca36204d15b17601ba6fc35dbdad950a647cc7"
|
||||
integrity sha512-+Nkvv0fHyhISkiMIjnyjmf5YJcQ1IQHZN6U9TLUMroWR38FNwpsC51Gb68yueafX1V6ifOisInSgP9WJFS13ew==
|
||||
|
||||
caseless@~0.12.0:
|
||||
version "0.12.0"
|
||||
|
@ -3952,10 +3952,10 @@ electron-to-chromium@^1.3.830:
|
|||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.843.tgz#671489bd2f59fd49b76adddc1aa02c88cd38a5c0"
|
||||
integrity sha512-OWEwAbzaVd1Lk9MohVw8LxMXFlnYd9oYTYxfX8KS++kLLjDfbovLOcEEXwRhG612dqGQ6+44SZvim0GXuBRiKg==
|
||||
|
||||
electron-to-chromium@^1.3.867:
|
||||
version "1.3.871"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.871.tgz#6e87365fd72037a6c898fb46050ad4be3ac9ef62"
|
||||
integrity sha512-qcLvDUPf8DSIMWarHT2ptgcqrYg62n3vPA7vhrOF24d8UNzbUBaHu2CySiENR3nEDzYgaN60071t0F6KLYMQ7Q==
|
||||
electron-to-chromium@^1.3.878:
|
||||
version "1.3.886"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.886.tgz#ac039c4001b665b1dd0f0ed9c2e4da90ff3c9267"
|
||||
integrity sha512-+vYdeBosI63VkCtNWnEVFjgNd/IZwvnsWkKyPtWAvrhA+XfByKoBJcbsMgudVU/bUcGAF9Xp3aXn96voWlc3oQ==
|
||||
|
||||
emmet@^2.1.5:
|
||||
version "2.3.4"
|
||||
|
@ -7729,10 +7729,10 @@ node-releases@^1.1.75:
|
|||
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.75.tgz#6dd8c876b9897a1b8e5a02de26afa79bb54ebbfe"
|
||||
integrity sha512-Qe5OUajvqrqDSy6wrWFmMwfJ0jVgwiw4T3KqmbTcZ62qW0gQkheXYhcFM1+lOVcGUoRxcEcfyvFMAnDgaF1VWw==
|
||||
|
||||
node-releases@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.0.tgz#67dc74903100a7deb044037b8a2e5f453bb05400"
|
||||
integrity sha512-aA87l0flFYMzCHpTM3DERFSYxc6lv/BltdbRTOMZuxZ0cwZCD3mejE5n9vLhSJCN++/eOqr77G1IO5uXxlQYWA==
|
||||
node-releases@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.1.tgz#3d1d395f204f1f2f29a54358b9fb678765ad2fc5"
|
||||
integrity sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==
|
||||
|
||||
node.extend@~2.0.2:
|
||||
version "2.0.2"
|
||||
|
@ -10224,10 +10224,10 @@ svelte@^3.44.0:
|
|||
resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.44.0.tgz#e6176cb3ad93846ddb4140e93f43098136b23f3b"
|
||||
integrity sha512-zWACSJBSncGiDvFfYOMFGNV5zDLOlyhftmO5yOZ0lEtQMptpElaRtl39MWz1+lYCpwUq4F3Q2lTzI9TrTL+eMA==
|
||||
|
||||
tailwindcss@^2.2.17:
|
||||
version "2.2.17"
|
||||
resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-2.2.17.tgz#c6332731f9ff1b6628ff589c95c38685347775e3"
|
||||
integrity sha512-WgRpn+Pxn7eWqlruxnxEbL9ByVRWi3iC10z4b6dW0zSdnkPVC4hPMSWLQkkW8GCyBIv/vbJ0bxIi9dVrl4CfoA==
|
||||
tailwindcss@^2.2.19:
|
||||
version "2.2.19"
|
||||
resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-2.2.19.tgz#540e464832cd462bb9649c1484b0a38315c2653c"
|
||||
integrity sha512-6Ui7JSVtXadtTUo2NtkBBacobzWiQYVjYW0ZnKaP9S1ZCKQ0w7KVNz+YSDI/j7O7KCMHbOkz94ZMQhbT9pOqjw==
|
||||
dependencies:
|
||||
arg "^5.0.1"
|
||||
bytes "^3.0.0"
|
||||
|
|
Loading…
Reference in a new issue