[next] Add CSS preprocessing (#1589)
* Add concept for style support in Astro * Update style preprocessor to use new compiler * fix: massage preprocessStyle type * fix: @astrojs/compiler types Co-authored-by: Nate Moore <nate@skypack.dev>
This commit is contained in:
parent
51e5a45ec5
commit
d1a73e4c38
18 changed files with 299 additions and 158 deletions
.changeset
docs/src/pages/guides
examples
minimal/src/pages
with-tailwindcss
packages
yarn.lock
5
.changeset/witty-cups-wave.md
Normal file
5
.changeset/witty-cups-wave.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'astro': patch
|
||||
---
|
||||
|
||||
Feat: add CSS preprocessors in Astro Next
|
|
@ -6,62 +6,140 @@ description: Learn how to style components with Astro.
|
|||
|
||||
Astro includes special handling to make writing CSS as easy as possible. Styling inside of Astro components is done by adding a `<style>` tag anywhere.
|
||||
|
||||
By default, all Astro component styles are **scoped**, meaning they only apply to the current component. These styles are automatically extracted and optimized for you in the final build, so that you don't need to worry about style loading.
|
||||
## Astro component styles
|
||||
|
||||
To create global styles, add a `:global()` wrapper around a selector (the same as if you were using [CSS Modules][css-modules]).
|
||||
By default, all Astro component styles are **scoped**, meaning they only apply to the current component. This can be very easy to work with, as you only have to worry about what’s in your current document at any given time.
|
||||
|
||||
```html
|
||||
<!-- src/components/MyComponent.astro -->
|
||||
<style>
|
||||
/* Scoped class selector within the component */
|
||||
.scoped {
|
||||
font-weight: bold;
|
||||
.text {
|
||||
font-family: cursive;
|
||||
}
|
||||
/* Scoped element selector within the component */
|
||||
h1 {
|
||||
color: red;
|
||||
}
|
||||
/* Global style */
|
||||
</style>
|
||||
|
||||
<h1>I’m a scoped style and I’m red!</h1>
|
||||
<p class="text">I'm a scoped style and I’m cursive!</p>
|
||||
```
|
||||
|
||||
Note that the `h1` selector won’t bleed out of the current component! These styles won’t apply any other `h1` tags outside this document. Not even child components.
|
||||
|
||||
_Tip: even though you can use element selectors, using classnames is preferred. This is not only slightly more performant, but is also easier to read, especially in a large document._
|
||||
|
||||
### Global styles
|
||||
|
||||
Of course, the real power of CSS is being able to reuse as much as possible! The preferred method of loading global styles is by using a standard `<link>` tag like you’re used to. It can even be used in conjunction with Astro’s scoped `<style>` tag:
|
||||
|
||||
```html
|
||||
<!-- src/pages/index.astro -->
|
||||
<head>
|
||||
<!-- load styles from src/styles/utils.css using Astro.resolve() -->
|
||||
<link rel="stylesheet" type="text/css"
|
||||
href={Astro.resolve('../styles/utils.css')} />
|
||||
</head>
|
||||
<body>
|
||||
<!-- scoped Astro styles that apply only to the current page (not to children or other components) -->
|
||||
<style>
|
||||
.title {
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- the ".title" class is scoped, but we can also use our global "align-center" and "margin top: 4" utility classes from utils.css -->
|
||||
<h1 class="title align-center mt4">Scoped Page Title</h1>
|
||||
</body>
|
||||
```
|
||||
|
||||
_Note: `Astro.resolve()` is a handy utility that helps resolve files from anywhere ([docs][astro-resolve])_
|
||||
|
||||
#### Styling children
|
||||
|
||||
If you’d like scoped styles to apply to children, you can use the special `:global()` function borrowed from [CSS Modules][css-modules]:
|
||||
|
||||
```astro
|
||||
<!-- src/components/MyComponent.astro -->
|
||||
---
|
||||
import PostContent from './Post.astro';
|
||||
---
|
||||
<style>
|
||||
/* Scoped to current component only */
|
||||
h1 {
|
||||
color: red;
|
||||
}
|
||||
|
||||
/* Scoped to all descendents of the scoped .blog-post class */
|
||||
.blog-post :global(h1) {
|
||||
color: blue;
|
||||
}
|
||||
</style>
|
||||
|
||||
<h1>Title</h1>
|
||||
<article class="blog-post">
|
||||
<PostContent />
|
||||
</article>
|
||||
```
|
||||
|
||||
This is a great way to style things like blog posts, or documents with CMS-powered content where the contents live outside of Astro. But be careful when styling children unconditionally, as it breaks component encapsulation. Components that appear different based on whether or not they have a certain parent component can become unwieldy quickly.
|
||||
|
||||
#### Global styles within style tag
|
||||
|
||||
If you’d like to use global styles but you don’t want to use a normal `<link>` tag (recommended), there is a `<style global>` escape hatch:
|
||||
|
||||
```html
|
||||
<style global>
|
||||
/* Applies to all h1 tags in your entire site */
|
||||
h1 {
|
||||
font-size: 32px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<h1>Globally-styled</h1>
|
||||
```
|
||||
|
||||
You can achieve the same by using the `:global()` function at the root of a selector:
|
||||
|
||||
```html
|
||||
<style>
|
||||
/* Applies to all h1 tags in your entire site */
|
||||
:global(h1) {
|
||||
font-size: 32px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="scoped">I'm a scoped style and only apply to this component</div>
|
||||
<h1>I have both scoped and global styles</h1>
|
||||
/* normal scoped h1 that applies to this file only */
|
||||
h1 {
|
||||
color: blue;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
To include every selector in a `<style>` as global styles, use `<style global>`. It's best to avoid using this escape hatch if possible, but it can be useful if you find yourself repeating `:global()` multiple times in the same `<style>`.
|
||||
It’s recommended to only use this in scenarios where a `<link>` tag won’t work. It’s harder to track down errant global styles when they’re scattered around and not in a central CSS file.
|
||||
|
||||
```html
|
||||
<!-- src/components/MyComponent.astro -->
|
||||
<style>
|
||||
/* Scoped class selector within the component */
|
||||
.scoped {
|
||||
font-weight: bold;
|
||||
}
|
||||
/* Scoped element selector within the component */
|
||||
h1 {
|
||||
color: red;
|
||||
}
|
||||
</style>
|
||||
📚 Read our full guide on [Astro component syntax][astro-component] to learn more about using the `<style>` tag.
|
||||
|
||||
<style global>
|
||||
/* Global style */
|
||||
h1 {
|
||||
font-size: 32px;
|
||||
}
|
||||
</style>
|
||||
## Autoprefixer
|
||||
|
||||
<div class="scoped">I'm a scoped style and only apply to this component</div>
|
||||
<h1>I have both scoped and global styles</h1>
|
||||
[Autoprefixer][autoprefixer] takes care of cross-browser CSS compatibility for you. Use it in astro by installing it (`npm install --save-dev autoprefixer`) and adding a `postcss.config.cjs` file to the root of your project:
|
||||
|
||||
```js
|
||||
// postcss.config.cjs
|
||||
module.exports = {
|
||||
autoprefixer: {
|
||||
/* (optional) autoprefixer settings */
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
📚 Read our full guide on [Astro component syntax](/core-concepts/astro-components#css-styles) to learn more about using the `<style>` tag.
|
||||
_Note: Astro v0.21 and later requires this manual setup for autoprefixer. Previous versions ran this automatically._
|
||||
|
||||
## Cross-Browser Compatibility
|
||||
## PostCSS
|
||||
|
||||
We also automatically add browser prefixes using [Autoprefixer][autoprefixer]. By default, Astro loads the [Browserslist defaults][browserslist-defaults], but you may also specify your own by placing a [Browserslist][browserslist] file in your project root.
|
||||
You can use any PostCSS plugin by adding a `postcss.config.cjs` file to the root of your project. Follow the documentation for the plugin you’re trying to install for configuration and setup.
|
||||
|
||||
---
|
||||
|
||||
|
@ -78,7 +156,7 @@ Styling in Astro is meant to be as flexible as you'd like it to be! The followin
|
|||
|
||||
¹ _`.astro` files have no runtime, therefore Scoped CSS takes the place of CSS Modules (styles are still scoped to components, but don't need dynamic values)_
|
||||
|
||||
All styles in Astro are automatically [**autoprefixed**](#cross-browser-compatibility), minified and bundled, so you can just write CSS and we'll handle the rest ✨.
|
||||
All styles in Astro are automatically minified and bundled, so you can just write CSS and we'll handle the rest ✨.
|
||||
|
||||
---
|
||||
|
||||
|
@ -104,33 +182,36 @@ Vue in Astro supports the same methods as `vue-loader` does:
|
|||
|
||||
Svelte in Astro also works exactly as expected: [Svelte Styling Docs][svelte-style].
|
||||
|
||||
### 👓 Sass
|
||||
### 🎨 CSS Preprocessors (Sass, Stylus, etc.)
|
||||
|
||||
Astro also supports [Sass][sass] out-of-the-box. To enable for each framework:
|
||||
Astro supports CSS preprocessors such as [Sass][sass], [Stylus][stylus], and [Less][less] through [Vite][vite-preprocessors]. It can be enabled via the following:
|
||||
|
||||
- **Sass**: Run `npm install -D sass` and use `<style lang="scss">` or `<style lang="sass">` (indented) in `.astro` files
|
||||
- **Stylus**: Run `npm install -D stylus` and use `<style lang="styl">` or `<style lang="stylus">` in `.astro` files
|
||||
- **Less**: Run `npm install -D less` and use `<style lang="less">` in `.astro` files.
|
||||
|
||||
You can also use all of the above within JS frameworks as well! Simply follow the patterns each framework recommends:
|
||||
|
||||
- **Astro**: `<style lang="scss">` or `<style lang="sass">`
|
||||
- **React** / **Preact**: `import Styles from './styles.module.scss'`;
|
||||
- **Vue**: `<style lang="scss">` or `<style lang="sass">`
|
||||
- **Svelte**: `<style lang="scss">` or `<style lang="sass">`
|
||||
- **Vue**: `<style lang="scss">`
|
||||
- **Svelte**: `<style lang="scss">`
|
||||
|
||||
💁 Sass is great! If you haven't used Sass in a while, please give it another try. The new and improved [Sass Modules][sass-use] are a great fit with modern web development, and it's blazing-fast since being rewritten in Dart. And the best part? **You know it already!** Use `.scss` to write familiar CSS syntax you're used to, and only sprinkle in Sass features if/when you need them.'
|
||||
Additionally, [PostCSS](#-postcss) is supported, but the setup is [slightly different](#-postcss).
|
||||
|
||||
**Note**: If you use .scss files rather than .css files, your stylesheet links should still point to .css files because of Astro’s auto-compilation process. When Astro “needs” the styling files, it’ll be “looking for” the final .css file(s) that it compiles from the .scss file(s). For example, if you have a .scss file at `./src/styles/global.scss`, use this link: `<link rel="stylesheet" href="{Astro.resolve('../styles/global.css')}">` — **not** `<link rel="stylesheet" href="{Astro.resolve('../styles/global.scss')}">`.
|
||||
_Note: CSS inside `public/` will **not** be transformed! Place it within `src/` instead._
|
||||
|
||||
### 🍃 Tailwind
|
||||
|
||||
> Note that Astro's Tailwind support _only_ works with Tailwind JIT mode.
|
||||
|
||||
Astro can be configured to use [Tailwind][tailwind] easily! Install the dependencies:
|
||||
|
||||
```
|
||||
npm install --save-dev tailwindcss
|
||||
```
|
||||
|
||||
And also create a `tailwind.config.js` in your project root:
|
||||
And create 2 files in your project root: `tailwind.config.cjs` and `postcss.config.cjs`:
|
||||
|
||||
```js
|
||||
// tailwind.config.js
|
||||
// tailwind.config.cjs
|
||||
module.exports = {
|
||||
mode: 'jit',
|
||||
purge: ['./public/**/*.html', './src/**/*.{astro,js,jsx,svelte,ts,tsx,vue}'],
|
||||
|
@ -138,15 +219,11 @@ module.exports = {
|
|||
};
|
||||
```
|
||||
|
||||
Be sure to add the config path to `astro.config.mjs`, so that Astro enables JIT support in the dev server.
|
||||
|
||||
```diff
|
||||
// astro.config.mjs
|
||||
export default {
|
||||
+ devOptions: {
|
||||
+ tailwindConfig: './tailwind.config.js',
|
||||
+ },
|
||||
};
|
||||
```js
|
||||
// postcss.config.cjs
|
||||
module.exports = {
|
||||
tailwind: {},
|
||||
};
|
||||
```
|
||||
|
||||
Now you're ready to write Tailwind! Our recommended approach is to create a `src/styles/global.css` file (or whatever you‘d like to name your global stylesheet) with [Tailwind utilities][tailwind-utilities] like so:
|
||||
|
@ -162,7 +239,7 @@ As an alternative to `src/styles/global.css`, You may also add Tailwind utilitie
|
|||
|
||||
#### Migrating from v0.19
|
||||
|
||||
As of [version 0.20.0](https://github.com/snowpackjs/astro/releases/tag/astro%400.20.0), Astro will no longer bundle, build and process `public/` files. Previously, we'd recommended putting your tailwind files in the `public/` directory. If you started a project with this pattern, you should move any Tailwind styles into the `src` directory and import them in your template using [Astro.resolve()](/reference/api-reference#astroresolve):
|
||||
As of [version 0.20.0](https://github.com/snowpackjs/astro/releases/tag/astro%400.20.0), Astro will no longer bundle, build and process `public/` files. Previously, we'd recommended putting your tailwind files in the `public/` directory. If you started a project with this pattern, you should move any Tailwind styles into the `src` directory and import them in your template using [Astro.resolve()][astro-resolve]:
|
||||
|
||||
```astro
|
||||
<link
|
||||
|
@ -171,44 +248,14 @@ As of [version 0.20.0](https://github.com/snowpackjs/astro/releases/tag/astro%40
|
|||
>
|
||||
```
|
||||
|
||||
### Importing from npm
|
||||
|
||||
If you want to import third-party libraries into an Astro component, you can use a `<style lang="scss">` tag to enable [Sass][sass] and use the [@use][sass-use] rule.
|
||||
|
||||
```html
|
||||
<!-- Loads Boostrap -->
|
||||
<style lang="scss">
|
||||
@use "bootstrap/scss/bootstrap";
|
||||
</style>
|
||||
```
|
||||
|
||||
### 🎭 PostCSS
|
||||
|
||||
[PostCSS](https://postcss.org/) is a popular CSS transpiler with support for [a huge ecosystem of plugins.](https://github.com/postcss/postcss#plugins)
|
||||
|
||||
**To use PostCSS with Snowpack:** add the [@snowpack/plugin-postcss](https://www.npmjs.com/package/@snowpack/plugin-postcss) plugin to your project.
|
||||
|
||||
```diff
|
||||
// snowpack.config.js
|
||||
"plugins": [
|
||||
+ "@snowpack/plugin-postcss"
|
||||
]
|
||||
```
|
||||
|
||||
PostCSS requires a [`postcss.config.js`](https://github.com/postcss/postcss#usage) file in your project. By default, the plugin looks in the root directory of your project, but you can customize this yourself with the `config` option. See [the plugin README](https://www.npmjs.com/package/@snowpack/plugin-postcss) for all available options.
|
||||
|
||||
```js
|
||||
// postcss.config.js
|
||||
// Example (empty) postcss config file
|
||||
module.exports = {
|
||||
plugins: [
|
||||
// ...
|
||||
],
|
||||
};
|
||||
```
|
||||
Using PostCSS is as simple as placing a [`postcss.config.cjs`](https://github.com/postcss/postcss#usage) file in the root of your project.
|
||||
|
||||
Be aware that this plugin will run on all CSS in your project, including any files that compiled to CSS (like `.scss` Sass files, for example).
|
||||
|
||||
_Note: CSS in `public/` **will not be transformed!** Instead, place it within `src/` if you’d like PostCSS to run over your styles._
|
||||
|
||||
## Bundling
|
||||
|
||||
All CSS is minified and bundled automatically for you in running `astro build`. Without getting too in the weeds, the general rules are:
|
||||
|
@ -550,6 +597,8 @@ This guide wouldn't be possible without the following blog posts, which expand o
|
|||
Also please check out the [Stylelint][stylelint] project to whip your styles into shape. You lint your JS, why not your CSS?
|
||||
|
||||
[autoprefixer]: https://github.com/postcss/autoprefixer
|
||||
[astro-component]: /core-concepts/astro-components#css-styles
|
||||
[astro-resolve]: /reference/api-reference#astroresolve
|
||||
[bem]: http://getbem.com/introduction/
|
||||
[box-model]: https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/The_box_model
|
||||
[browserslist]: https://github.com/browserslist/browserslist
|
||||
|
@ -560,6 +609,7 @@ Also please check out the [Stylelint][stylelint] project to whip your styles int
|
|||
[css-treeshaking]: https://css-tricks.com/how-do-you-remove-unused-css-from-a-site/
|
||||
[fouc]: https://en.wikipedia.org/wiki/Flash_of_unstyled_content
|
||||
[layout-isolated]: https://web.archive.org/web/20210227162315/https://visly.app/blogposts/layout-isolated-components
|
||||
[less]: https://lesscss.org/
|
||||
[issues]: https://github.com/snowpackjs/astro/issues
|
||||
[magic-number]: https://css-tricks.com/magic-numbers-in-css/
|
||||
[material-ui]: https://material.io/components
|
||||
|
@ -568,11 +618,13 @@ Also please check out the [Stylelint][stylelint] project to whip your styles int
|
|||
[sass-use]: https://sass-lang.com/documentation/at-rules/use
|
||||
[smacss]: http://smacss.com/
|
||||
[styled-components]: https://styled-components.com/
|
||||
[stylus]: https://stylus-lang.com/
|
||||
[styled-jsx]: https://github.com/vercel/styled-jsx
|
||||
[stylelint]: https://stylelint.io/
|
||||
[svelte-style]: https://svelte.dev/docs#style
|
||||
[tailwind]: https://tailwindcss.com
|
||||
[tailwind-utilities]: https://tailwindcss.com/docs/adding-new-utilities#using-css
|
||||
[utility-css]: https://frontstuff.io/in-defense-of-utility-first-css
|
||||
[vite-preprocessors]: https://vitejs.dev/guide/features.html#css-pre-processors
|
||||
[vue-css-modules]: https://vue-loader.vuejs.org/guide/css-modules.html
|
||||
[vue-scoped]: https://vue-loader.vuejs.org/guide/scoped-css.html
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<title>Welcome to Astro</title>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
|
|
@ -8,10 +8,6 @@
|
|||
|
||||
// @ts-check
|
||||
export default /** @type {import('astro').AstroUserConfig} */ ({
|
||||
// Enable Tailwind by telling Astro where your Tailwind config file lives.
|
||||
devOptions: {
|
||||
tailwindConfig: './tailwind.config.js',
|
||||
},
|
||||
// Enable the Preact renderer to support Preact JSX components.
|
||||
renderers: ['@astrojs/renderer-preact'],
|
||||
});
|
||||
|
|
|
@ -9,8 +9,9 @@
|
|||
"preview": "astro preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"tailwindcss": "^2.1.2",
|
||||
"astro": "^0.20.12"
|
||||
"astro": "^0.20.12",
|
||||
"autoprefixer": "^10.3.7",
|
||||
"tailwindcss": "^2.2.17"
|
||||
},
|
||||
"snowpack": {
|
||||
"workspaceRoot": "../.."
|
||||
|
|
6
examples/with-tailwindcss/postcss.config.js
Normal file
6
examples/with-tailwindcss/postcss.config.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
|
@ -1,4 +1,4 @@
|
|||
module.exports = {
|
||||
mode: 'jit',
|
||||
purge: ['./public/**/*.html', './src/**/*.{astro,js,jsx,ts,tsx,vue,svelte}'],
|
||||
purge: ['**/*.{astro,html,js,jsx,svelte,ts,tsx,vue}'],
|
||||
};
|
||||
|
|
|
@ -55,7 +55,7 @@
|
|||
"lerna": "^4.0.0",
|
||||
"prettier": "^2.4.1",
|
||||
"tiny-glob": "^0.2.8",
|
||||
"typescript": "^4.4.3"
|
||||
"typescript": "^4.4.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
|
|
|
@ -79,7 +79,6 @@
|
|||
"node-fetch": "^2.6.5",
|
||||
"path-to-regexp": "^6.2.0",
|
||||
"remark-slug": "^7.0.0",
|
||||
"sass": "^1.43.2",
|
||||
"semver": "^7.3.5",
|
||||
"send": "^0.17.1",
|
||||
"shiki": "^0.9.10",
|
||||
|
@ -109,7 +108,8 @@
|
|||
"@types/yargs-parser": "^20.2.1",
|
||||
"chai": "^4.3.4",
|
||||
"cheerio": "^1.0.0-rc.10",
|
||||
"mocha": "^9.1.3"
|
||||
"mocha": "^9.1.3",
|
||||
"sass": "^1.43.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0",
|
||||
|
|
|
@ -81,8 +81,6 @@ export interface AstroUserConfig {
|
|||
hostname?: string;
|
||||
/** The port to run the dev server on. */
|
||||
port?: number;
|
||||
/** Path to tailwind.config.js, if used */
|
||||
tailwindConfig?: string;
|
||||
/**
|
||||
* Configure The trailing slash behavior of URL route matching:
|
||||
* 'always' - Only match URLs that include a trailing slash (ex: "/foo/")
|
||||
|
|
|
@ -63,7 +63,6 @@ export const AstroConfigSchema = z.object({
|
|||
.object({
|
||||
hostname: z.string().optional().default('localhost'),
|
||||
port: z.number().optional().default(3000),
|
||||
tailwindConfig: z.string().optional(),
|
||||
trailingSlash: z
|
||||
.union([z.literal('always'), z.literal('never'), z.literal('ignore')])
|
||||
.optional()
|
||||
|
|
|
@ -16,6 +16,13 @@ import { getPackageJSON, parseNpmName } from './util.js';
|
|||
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
/**
|
||||
* Tailwind fixes
|
||||
* These fix Tailwind HMR in dev, and must be declared before Vite initiates.
|
||||
* These are Tailwind-specific, so they’re safe to add.
|
||||
*/
|
||||
(process.env as any).TAILWIND_MODE = 'watch';
|
||||
|
||||
// note: ssr is still an experimental API hence the type omission
|
||||
type ViteConfigWithSSR = vite.InlineConfig & { ssr?: { external?: string[]; noExternal?: string[] } };
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ if (import.meta.hot) {
|
|||
const doc = parser.parseFromString(html, 'text/html');
|
||||
|
||||
morphdom(document.head, doc.head, {
|
||||
onBeforeElUpdated: function (fromEl, toEl) {
|
||||
onBeforeElUpdated(fromEl, toEl) {
|
||||
if (fromEl.isEqualNode(toEl)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ if (import.meta.hot) {
|
|||
});
|
||||
|
||||
morphdom(document.body, doc.body, {
|
||||
onBeforeElUpdated: function (fromEl, toEl) {
|
||||
onBeforeElUpdated(fromEl, toEl) {
|
||||
if (fromEl.localName === 'astro-root') {
|
||||
return fromEl.getAttribute('uid') !== toEl.getAttribute('uid');
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import type { TransformResult } from '@astrojs/compiler';
|
||||
import type { Plugin } from '../core/vite';
|
||||
import type vite from '../core/vite';
|
||||
import type { AstroConfig } from '../@types/astro-core';
|
||||
|
||||
import esbuild from 'esbuild';
|
||||
|
@ -8,6 +8,7 @@ import { fileURLToPath } from 'url';
|
|||
import { transform } from '@astrojs/compiler';
|
||||
import { decode } from 'sourcemap-codec';
|
||||
import { AstroDevServer } from '../core/dev/index.js';
|
||||
import { getViteTransform, TransformHook, transformWithVite } from './styles.js';
|
||||
|
||||
interface AstroPluginOptions {
|
||||
config: AstroConfig;
|
||||
|
@ -15,10 +16,14 @@ interface AstroPluginOptions {
|
|||
}
|
||||
|
||||
/** Transform .astro files for Vite */
|
||||
export default function astro({ config, devServer }: AstroPluginOptions): Plugin {
|
||||
export default function astro({ config, devServer }: AstroPluginOptions): vite.Plugin {
|
||||
let viteTransform: TransformHook;
|
||||
return {
|
||||
name: '@astrojs/vite-plugin-astro',
|
||||
enforce: 'pre', // run transforms before other plugins can
|
||||
configResolved(resolvedConfig) {
|
||||
viteTransform = getViteTransform(resolvedConfig);
|
||||
},
|
||||
// note: don’t claim .astro files with resolveId() — it prevents Vite from transpiling the final JS (import.meta.globEager, etc.)
|
||||
async load(id) {
|
||||
if (!id.endsWith('.astro')) {
|
||||
|
@ -40,6 +45,15 @@ export default function astro({ config, devServer }: AstroPluginOptions): Plugin
|
|||
sourcefile: id,
|
||||
sourcemap: 'both',
|
||||
internalURL: 'astro/internal',
|
||||
preprocessStyle: async (value: string, attrs: Record<string, string>) => {
|
||||
if (!attrs || !attrs.lang) return null;
|
||||
const result = await transformWithVite(value, attrs, id, viteTransform);
|
||||
if (!result) {
|
||||
// TODO: compiler supports `null`, but types don't yet
|
||||
return (result as any);
|
||||
}
|
||||
return { code: result.code, map: result.map?.toString() }
|
||||
},
|
||||
});
|
||||
// Compile `.ts` to `.js`
|
||||
const { code, map } = await esbuild.transform(tsResult.code, { loader: 'ts', sourcemap: 'external', sourcefile: id });
|
||||
|
@ -64,7 +78,8 @@ export default function astro({ config, devServer }: AstroPluginOptions): Plugin
|
|||
return devServer.handleHotUpdate(context);
|
||||
}
|
||||
},
|
||||
transformIndexHtml(html) {
|
||||
transformIndexHtml() {
|
||||
// note: this runs only in dev
|
||||
return [
|
||||
{
|
||||
injectTo: 'head-prepend',
|
||||
|
|
22
packages/astro/src/vite-plugin-astro/styles.ts
Normal file
22
packages/astro/src/vite-plugin-astro/styles.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
import type vite from '../core/vite';
|
||||
|
||||
export type TransformHook = (code: string, id: string, ssr?: boolean) => Promise<vite.TransformResult>;
|
||||
|
||||
// https://vitejs.dev/guide/features.html#css-pre-processors
|
||||
const SUPPORTED_PREPROCESSORS = new Set(['scss', 'sass', 'styl', 'stylus', 'less']);
|
||||
|
||||
/** Load vite:css’ transform() hook */
|
||||
export function getViteTransform(viteConfig: vite.ResolvedConfig): TransformHook {
|
||||
const viteCSSPlugin = viteConfig.plugins.find(({ name }) => name === 'vite:css');
|
||||
if (!viteCSSPlugin) throw new Error(`vite:css plugin couldn’t be found`);
|
||||
if (!viteCSSPlugin.transform) throw new Error(`vite:css has no transform() hook`);
|
||||
return viteCSSPlugin.transform.bind(null as any) as any;
|
||||
}
|
||||
|
||||
/** Transform style using Vite hook */
|
||||
export async function transformWithVite(value: string, attrs: Record<string, string>, id: string, transformHook: TransformHook): Promise<vite.TransformResult | null> {
|
||||
const lang = (attrs.lang || '').toLowerCase(); // don’t be case-sensitive
|
||||
if (!SUPPORTED_PREPROCESSORS.has(lang)) return null; // only preprocess the above
|
||||
const result = await transformHook(value, id.replace(/\.astro$/, `.${lang}`));
|
||||
return result || null;
|
||||
}
|
|
@ -1,19 +1,7 @@
|
|||
/**
|
||||
* UNCOMMENT: Add styles support
|
||||
import { expect } from 'chai';
|
||||
import cheerio from 'cheerio';
|
||||
import { loadFixture } from './test-utils.js';
|
||||
|
||||
/** Basic CSS minification; removes some flakiness in testing CSS *\/
|
||||
function cssMinify(css) {
|
||||
return css
|
||||
.trim() // remove whitespace
|
||||
.replace(/\r?\n\s*\/g, '') // collapse lines
|
||||
.replace(/\s*\{/g, '{') // collapse selectors
|
||||
.replace(/:\s*\/g, ':') // collapse attributes
|
||||
.replace(/;}/g, '}'); // collapse block
|
||||
}
|
||||
|
||||
let fixture;
|
||||
|
||||
before(async () => {
|
||||
|
@ -21,26 +9,26 @@ before(async () => {
|
|||
await fixture.build();
|
||||
});
|
||||
|
||||
|
||||
describe('Styles SSR', () => {
|
||||
it('Has <link> tags', async () => {
|
||||
const MUST_HAVE_LINK_TAGS = [
|
||||
'/src/components/ReactCSS.css',
|
||||
'/src/components/ReactModules.module.css',
|
||||
'/src/components/SvelteScoped.css',
|
||||
'/src/components/VueCSS.css',
|
||||
'/src/components/VueModules.css',
|
||||
'/src/components/VueScoped.css',
|
||||
];
|
||||
// TODO: convert <style> to <link>
|
||||
// it('Has <link> tags', async () => {
|
||||
// const MUST_HAVE_LINK_TAGS = [
|
||||
// '/src/components/ReactCSS.css',
|
||||
// '/src/components/ReactModules.module.css',
|
||||
// '/src/components/SvelteScoped.css',
|
||||
// '/src/components/VueCSS.css',
|
||||
// '/src/components/VueModules.css',
|
||||
// '/src/components/VueScoped.css',
|
||||
// ];
|
||||
|
||||
const html = await fixture.readFile('/index.html');
|
||||
const $ = cheerio.load(html);
|
||||
// const html = await fixture.readFile('/index.html');
|
||||
// const $ = cheerio.load(html);
|
||||
|
||||
for (const href of MUST_HAVE_LINK_TAGS) {
|
||||
const el = $(`link[href="${href}"]`);
|
||||
expect(el).to.have.lengthOf(1);
|
||||
}
|
||||
});
|
||||
// for (const href of MUST_HAVE_LINK_TAGS) {
|
||||
// const el = $(`link[href="${href}"]`);
|
||||
// expect(el).to.have.lengthOf(1);
|
||||
// }
|
||||
// });
|
||||
|
||||
it('Has correct CSS classes', async () => {
|
||||
const html = await fixture.readFile('/index.html');
|
||||
|
@ -80,29 +68,28 @@ describe('Styles SSR', () => {
|
|||
});
|
||||
|
||||
it('CSS Module support in .astro', async () => {
|
||||
const html = await fixture.readFile('/');
|
||||
const html = await fixture.readFile('/index.html');
|
||||
const $ = cheerio.load(html);
|
||||
|
||||
let scopedClass;
|
||||
|
||||
// test 1: <style> tag in <head> is transformed
|
||||
const css = cssMinify(
|
||||
$('style')
|
||||
.html()
|
||||
.replace(/\.astro-[A-Za-z0-9-]+/, (match) => {
|
||||
scopedClass = match; // get class hash from result
|
||||
return match;
|
||||
})
|
||||
);
|
||||
const css = $('style')
|
||||
.html()
|
||||
.replace(/\.astro-[A-Za-z0-9-]+/, (match) => {
|
||||
scopedClass = match; // get class hash from result
|
||||
return match;
|
||||
});
|
||||
|
||||
expect(css).to.equal(`.wrapper${scopedClass}{margin-left:auto;margin-right:auto;max-width:1200px}`);
|
||||
expect(css).to.equal(`.wrapper${scopedClass}{margin-left:auto;margin-right:auto;max-width:1200px;}.outer${scopedClass}{color:red;}`);
|
||||
|
||||
// test 2: element received .astro-XXXXXX class (this selector will succeed if transformed correctly)
|
||||
const wrapper = $(`.wrapper${scopedClass}`);
|
||||
expect(wrapper).to.have.lengthOf(1);
|
||||
});
|
||||
|
||||
it('Astro scoped styles', async () => {
|
||||
// TODO: fix compiler bug
|
||||
it.skip('Astro scoped styles', async () => {
|
||||
const html = await fixture.readFile('/index.html');
|
||||
const $ = cheerio.load(html);
|
||||
|
||||
|
@ -128,7 +115,7 @@ describe('Styles SSR', () => {
|
|||
const { contents: css } = await fixture.fetch('/src/components/Astro.astro.css').then((res) => res.text());
|
||||
|
||||
// test 4: CSS generates as expected
|
||||
expect(cssMinify(css.toString())).to.equal(`.blue.${scopedClass}{color:powderblue}.color\\:blue.${scopedClass}{color:powderblue}.visible.${scopedClass}{display:block}`);
|
||||
expect(css.toString()).to.equal(`.blue.${scopedClass}{color:powderblue}.color\\:blue.${scopedClass}{color:powderblue}.visible.${scopedClass}{display:block}`);
|
||||
});
|
||||
|
||||
it('Astro scoped styles skipped without <style>', async () => {
|
||||
|
@ -139,7 +126,8 @@ describe('Styles SSR', () => {
|
|||
expect($('#no-scope').attr('class')).to.equal(undefined);
|
||||
});
|
||||
|
||||
it('Astro scoped styles can be passed to child components', async () => {
|
||||
// TODO: fix compiler bug
|
||||
it.skip('Astro scoped styles can be passed to child components', async () => {
|
||||
const html = await fixture.readFile('/index.html');
|
||||
const $ = cheerio.load(html);
|
||||
|
||||
|
@ -154,7 +142,3 @@ describe('Styles SSR', () => {
|
|||
expect($('#passed-in').attr('class')).to.equal(`outer ${scopedClass}`);
|
||||
});
|
||||
});
|
||||
|
||||
*/
|
||||
|
||||
it.skip('is skipped', () => {});
|
||||
|
|
|
@ -12,7 +12,6 @@ export const createConfig = ({ renderers }: { renderers: string[] }) => {
|
|||
devOptions: {
|
||||
// hostname: 'localhost', // The hostname to run the dev server on.
|
||||
// port: 3000, // The port to run the dev server on.
|
||||
// tailwindConfig: '', // Path to tailwind.config.js if used, e.g. './tailwind.config.js'
|
||||
},`,
|
||||
` renderers: ${JSON.stringify(renderers, undefined, 2)
|
||||
.split('\n')
|
||||
|
|
68
yarn.lock
68
yarn.lock
|
@ -2553,6 +2553,18 @@ 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==
|
||||
dependencies:
|
||||
browserslist "^4.17.3"
|
||||
caniuse-lite "^1.0.30001264"
|
||||
fraction.js "^4.1.1"
|
||||
normalize-range "^0.1.2"
|
||||
picocolors "^0.2.1"
|
||||
postcss-value-parser "^4.1.0"
|
||||
|
||||
available-typed-arrays@^1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7"
|
||||
|
@ -2774,6 +2786,17 @@ 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==
|
||||
dependencies:
|
||||
caniuse-lite "^1.0.30001265"
|
||||
electron-to-chromium "^1.3.867"
|
||||
escalade "^3.1.1"
|
||||
node-releases "^2.0.0"
|
||||
picocolors "^1.0.0"
|
||||
|
||||
buffer-crc32@~0.2.3:
|
||||
version "0.2.13"
|
||||
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
|
||||
|
@ -2918,6 +2941,11 @@ 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==
|
||||
|
||||
caseless@~0.12.0:
|
||||
version "0.12.0"
|
||||
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
|
||||
|
@ -3939,6 +3967,11 @@ 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==
|
||||
|
||||
emmet@^2.1.5:
|
||||
version "2.3.4"
|
||||
resolved "https://registry.yarnpkg.com/emmet/-/emmet-2.3.4.tgz#5ba0d7a5569a68c7697dfa890c772e4f3179d123"
|
||||
|
@ -4926,6 +4959,11 @@ form-fix-array@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/form-fix-array/-/form-fix-array-1.0.0.tgz#a1347a47e53117ab7bcdbf3e2f3ec91c66769bc8"
|
||||
integrity sha1-oTR6R+UxF6t7zb8+Lz7JHGZ2m8g=
|
||||
|
||||
fraction.js@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.1.1.tgz#ac4e520473dae67012d618aab91eda09bcb400ff"
|
||||
integrity sha512-MHOhvvxHTfRFpF1geTK9czMIZ6xclsEor2wkIGYYq+PxcQqT7vStJqjhe6S1TenZrMZzo+wlqOufBDVepUEgPg==
|
||||
|
||||
fresh@0.5.2, fresh@~0.5.2:
|
||||
version "0.5.2"
|
||||
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
|
||||
|
@ -7739,6 +7777,11 @@ 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.extend@~2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/node.extend/-/node.extend-2.0.2.tgz#b4404525494acc99740f3703c496b7d5182cc6cc"
|
||||
|
@ -7808,6 +7851,11 @@ normalize-path@^3.0.0, normalize-path@~3.0.0:
|
|||
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
|
||||
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
|
||||
|
||||
normalize-range@^0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942"
|
||||
integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=
|
||||
|
||||
normalize-url@^6.1.0:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a"
|
||||
|
@ -8565,6 +8613,11 @@ picocolors@^0.2.1:
|
|||
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-0.2.1.tgz#570670f793646851d1ba135996962abad587859f"
|
||||
integrity sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==
|
||||
|
||||
picocolors@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
|
||||
integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
|
||||
|
||||
picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.2.3:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972"
|
||||
|
@ -10266,10 +10319,10 @@ svelte@^3.42.3:
|
|||
resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.42.6.tgz#45a534d76fcdb551a2f23abf2cfee648fa248d03"
|
||||
integrity sha512-lAcryr9Do2PeGtbodspX5I4kWj4yWYAa2WGpDCwzNkP3y8WZTxigMd4/TMO1rBZEOkMYGn4ZXrbAlSEGhK6q3w==
|
||||
|
||||
tailwindcss@^2.1.2:
|
||||
version "2.2.15"
|
||||
resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-2.2.15.tgz#8bee3ebe68b988c050508ce20633f35b040dd9fe"
|
||||
integrity sha512-WgV41xTMbnSoTNMNnJvShQZ+8GmY86DmXTrCgnsveNZJdlybfwCItV8kAqjYmU49YiFr+ofzmT1JlAKajBZboQ==
|
||||
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==
|
||||
dependencies:
|
||||
arg "^5.0.1"
|
||||
bytes "^3.0.0"
|
||||
|
@ -10647,11 +10700,16 @@ typedarray@^0.0.6:
|
|||
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
||||
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
|
||||
|
||||
typescript@^4.3.1-rc, typescript@^4.3.5, typescript@^4.4.3:
|
||||
typescript@^4.3.1-rc, typescript@^4.3.5:
|
||||
version "4.4.3"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.3.tgz#bdc5407caa2b109efd4f82fe130656f977a29324"
|
||||
integrity sha512-4xfscpisVgqqDfPaJo5vkd+Qd/ItkoagnHpufr+i2QCHBsNYp+G7UAoyFl8aPtx879u38wPV65rZ8qbGZijalA==
|
||||
|
||||
typescript@^4.4.4:
|
||||
version "4.4.4"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.4.tgz#2cd01a1a1f160704d3101fd5a58ff0f9fcb8030c"
|
||||
integrity sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==
|
||||
|
||||
uglify-js@^3.1.4:
|
||||
version "3.14.2"
|
||||
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.14.2.tgz#d7dd6a46ca57214f54a2d0a43cad0f35db82ac99"
|
||||
|
|
Loading…
Add table
Reference in a new issue