Compare commits

...
Sign in to create a new pull request.

232 commits

Author SHA1 Message Date
Adam Wathan
cd10f5d587 Conditionally invoke transform functions instead of using no-op values 2023-12-01 21:42:52 -05:00
Jordan Pittman
6dfcdd1007
Fix loading of built-in plugins when using an ESM or TypeScript config with the Standalone CLI (#12506) 2023-12-01 11:44:30 -05:00
Jordan Pittman
317fba1d6b
Fix source maps of variant utilities that come from an @layer rule (#12508)
* Refactor

* Keep traversing sibling nodes

* Make sure the root node has a source location for the end

* Add source map test for at-rule variants

* Update changelog
2023-12-01 11:05:34 -05:00
Jordan Pittman
3169d159ca
Fix generation of utilities that use slashes in arbitrary modifiers (#12515)
* Fix support for slashes in arbitrary modifiers

* Update changelog
2023-12-01 10:44:08 -05:00
Jordan Pittman
b2b37e04e4
Don't crash when given applying a variant to a negated version of a simple utility (#12514)
* Don't crash when given applying a variant to a negated version of a simple utility

* Update changelog
2023-12-01 10:22:45 -05:00
depfu[bot]
dac9cf23a2 Update parcel to version 2.10.3 2023-11-30 05:27:38 +00:00
Adam Wathan
767b78cc30 Update changelog 2023-11-27 09:13:56 -05:00
Ngô Quốc Đạt
bce0481f40
Add logical properties support for float and clear (#12480) 2023-11-27 09:12:58 -05:00
Robin Malfait
ec0fe42968
sync package-lock.json 2023-11-27 13:36:22 +01:00
Anton Kastritskii
f75c3988b3
update lilconfig & postcss-load-config (#12455) 2023-11-27 13:36:06 +01:00
depfu[bot]
16d25c9746
Update lilconfig to version 3.0.0 (#12484)
Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2023-11-27 13:08:59 +01:00
depfu[bot]
9c70df1539
Update vite to version 5.0.2 (#12477)
Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2023-11-27 13:05:59 +01:00
depfu[bot]
4ef0274b44 Update postcss-load-config to version 4.0.2 2023-11-27 01:14:25 +00:00
depfu[bot]
304c2bad6c Update @swc/cli to version 0.1.63 2023-11-23 05:44:05 +00:00
depfu[bot]
eb2fe9494b
Update rollup to version 4.5.0 (#12458)
Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2023-11-21 06:46:44 -05:00
Adam Wathan
8b2c6e7684 Fix typo in changelog 2023-11-17 12:12:24 -05:00
Jordan Pittman
95d46a0e5d Update lockfile 2023-11-16 15:39:05 +00:00
Jordan Pittman
308c03b11b Update browserslist 2023-11-16 15:39:05 +00:00
Jordan Pittman
79e57ecb49 Update sourcemaps 2023-11-16 15:39:05 +00:00
Jordan Pittman
0a2aea2c9b Update test 2023-11-16 15:39:05 +00:00
Jordan Pittman
b4f3cddb50 Remove uses of ::-webkit-backdrop 2023-11-16 15:39:05 +00:00
depfu[bot]
7385f48e46 Update lightningcss to version 1.22.1 2023-11-16 15:39:05 +00:00
depfu[bot]
a42354c743 Update sass to version 1.69.5 2023-11-14 23:58:10 +00:00
depfu[bot]
166a7a417d Update fast-glob to version 3.3.2 2023-11-13 15:29:37 +00:00
Adam Wathan
e5eceb3879
Add appearance-auto utility (#12404)
* Add `appearance-auto` utility

* Update CHANGELOG

---------

Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
2023-11-12 11:22:35 -05:00
Robin Malfait
a5a61580cd
Improve candidate detection in minified JS arrays (without spaces) (#12396)
* add test to verify `["util1","util2"]` works

* update extractor regex, to reduce valid values in the arbitrary value part

Co-authored-by: Autom <mijnnaamis2112@hotmail.com>

* add special case with deeply nested `[]`

* update changelog

* move oxide changelog itemsto the bottom

---------

Co-authored-by: Autom <mijnnaamis2112@hotmail.com>
2023-11-10 23:06:02 +01:00
depfu[bot]
1648b864c1 Update parcel to version 2.10.2 2023-11-09 05:32:22 +00:00
depfu[bot]
6fab6d700a Update @napi-rs/cli to version 2.16.5 2023-11-08 10:30:40 +00:00
depfu[bot]
0b7f27c19a Update jiti to version 1.21.0 2023-11-06 20:05:40 +00:00
depfu[bot]
803d7b582e Update eslint to version 8.52.0 2023-11-03 21:46:24 +00:00
Luke Warlow
b411d04c8c
Add forced-colors variant (#11694)
* Add forced-colors variant

Also add a contrast-custom variant to match custom contrast preferences

* use `toMatchSnapshot` instead of `toMatchFormattedCss`

More info: https://github.com/tailwindlabs/tailwindcss/pull/12170

* remove `contrast-custom` variant

* move `forcedColorsVariants` next to `prefersContrastVariants`

* update changelog

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2023-11-02 20:28:50 +01:00
depfu[bot]
903eb5629c Update turbo to version 1.10.16 2023-11-02 17:05:07 +00:00
Luke Warlow
6353450a94
Add forced-color-adjust utilities (#11931)
* Add forced-colors-adjust utilities

* Update forcedColorsAdjust.test.js

* use `toMatchSnapshot` instead of `toMatchFormattedCss`

More info: https://github.com/tailwindlabs/tailwindcss/pull/12170

* rename `forced-colors-adjust` -> `forced-color-adjust`

Dropped the `s` in `colors`.

* update changelog

* fix typo

* use full `forced-color-adjust-auto` and `forced-color-adjust-none` names

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2023-11-02 17:48:11 +01:00
Tom
2d6856c8eb
Ensure configured font-feature-settings for mono are included in Preflight (#12342)
* Use the default font-feature-settings for mono

* Update changelog

* Update tests

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2023-11-02 10:57:35 -04:00
depfu[bot]
c73b741a24 Update @swc/core to version 1.3.95 2023-11-02 09:51:31 +00:00
depfu[bot]
a8ab341f1e Update vite to version 4.5.0 2023-11-02 08:43:21 +00:00
Jordan Pittman
b7bafaaf85 Update changelog 2023-10-30 14:48:26 -04:00
Jordan Pittman
cf97c2274a
Don’t output @config in CSS file after a rebuild (#12327) 2023-10-30 14:37:49 -04:00
Jordan Pittman
0985d26a54
Don’t add spaces to negative numbers following a comma (#12324)
* Don’t add spaces to negative numbers following a comma

* Update changelog
2023-10-30 10:47:24 -04:00
Jordan Pittman
8f788e27b0 Tweak comment 2023-10-30 09:09:00 -04:00
Adam Wathan
3c08490c96 Organize classic engine vs. Oxide engine entries in CHANGELOG 2023-10-30 07:58:36 -04:00
Adam Wathan
28e96baf3a Add Oxide parser to the changelog 2023-10-29 14:23:14 -04:00
Nikita Gaidakov
02da2b82a1
Improve resolveConfig return type: merge themes (#12272)
* Generate types: do not intersect with Config theme type when generating DefaultTheme

* Merge default theme in ResolvedConfig

* UnwrapResolvables on theme.extend as well

* Apply extend to overrides and default theme

* Omit extend from DefaultTheme

* Relax generic constraints, better generic variable names

* Fall back to ThemeConfig if key not in DefaultTheme

* Split out ThemeConfigCustomizable to avoid anys in ThemeConfigResolved

* Allow custom theme properties

* handle TypeScript error

* apply prettier formatting

* update changelog

* change type name

---------

Co-authored-by: Nikita Gaidakov <ngaidakov@podfather.com>
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2023-10-27 15:46:02 +02:00
Jordan Pittman
782c733b90
Add spacing scale to min-width, min-height, and max-width (#12300)
* Add spacing scale to `min-width`, `min-height`, and `max-width`

* Update snapshots

* Update changelog
2023-10-26 14:14:43 -04:00
Jordan Pittman
97e89d76a5
Disable tap highlights on iOS (#12299)
* Disable tap highlights on iOS

* Update changelog

* Update snapshots

* Update changelog
2023-10-26 13:09:38 -04:00
Adam Wathan
662905e5e3
Add subgrid utilities (#12298)
* Add subgrid utilities

* Update CHANGELOG

---------

Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
2023-10-26 12:52:39 -04:00
depfu[bot]
e13ae7b725
Update rollup to version 4.1.4 (#12295)
Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2023-10-26 09:15:27 -04:00
Ahmed Abdelbaset
c92596f549
Fix typo in size-* tests (#12289)
* Update size.test.js

* Fix test

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2023-10-25 13:54:28 -04:00
Jordan Pittman
e4b32a7a32
Add size-* shorthand (#12287)
* Add `size-*` shorthand

Add plugin tests

Adjust options

wip

* Update changelog
2023-10-25 11:54:37 -04:00
Robin Malfait
9d83400a51
update CHANGELOG 2023-10-25 15:40:05 +02:00
Robin Malfait
761717cb4c
Fix incorrect spaces around - in calc() expression (#12283)
* sync package-lock.json

* prevent formatting inside `env()` when formatting `calc` function

* prevent formatting keywords in `calc`

* add dedicated normalization tests

* `calc()` in `env()` should be formatted

* update changelog
2023-10-25 15:20:49 +02:00
Jordan Pittman
d1317d5a8c Sync changelog 2023-10-24 14:11:56 -04:00
Robin Malfait
90047b4532
update changelog 2023-10-24 14:11:33 +02:00
Robin Malfait
89ec6fbe67
Improve automatic var injection (#12236)
* prevent automatic var injection for properties that accept `<dashed-ident>` for the value

* add test

* add `font-palette`

* improve readability
2023-10-24 14:08:46 +02:00
Jordan Pittman
4a8f584176
Add types for the nesting plugin (#12269)
* Add types for the nesting plugin

* Update changelog
2023-10-23 15:03:28 -04:00
Marco Carvalho
bcfb38d5f9
Refactor length function to utilize a pre-compiled RegExp (#12251)
* Refactor length function to utilize a pre-compiled RegExp

* Update dataTypes.js
2023-10-20 13:50:42 +02:00
depfu[bot]
4e1a26aad0 Update sass to version 1.69.4 2023-10-20 01:59:33 +00:00
depfu[bot]
d26a51fae8 Update parcel to version 2.10.0 2023-10-18 07:15:32 +00:00
depfu[bot]
56d8a0b598 Update resolve to version 1.22.8 2023-10-18 01:19:00 +00:00
2hu
a1bcfa2479
Allow plugins from a parent document to be used in an iframe (#12208)
* Fix cross iframe isPlainObject check

* Update changelog

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2023-10-17 10:41:53 -04:00
Jordan Pittman
0b0862b20c
Fix crash when watching renamed files on FreeBSD (#12193)
* Fix file watching bug when renaming files on FreeBSD

* Update changelog

* Add delay to test

* Bump delay

* Update test
2023-10-17 09:50:35 -04:00
Simon He
e9ca7ab453
Speed up CSS math function check (#12215)
* perf(cssMathFnRE): Dynamically create regular rules

* Cleanup code

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2023-10-16 20:18:17 -04:00
depfu[bot]
44fb67b98d Update eslint to version 8.51.0 2023-10-13 21:49:58 +00:00
depfu[bot]
e8a50fb120 Update turbo to version 1.10.15 2023-10-13 20:34:40 +00:00
depfu[bot]
27b6530599 Update @swc/core to version 1.3.92 2023-10-12 09:49:09 +00:00
Adam Wathan
0170662c3c Update CHANGELOG 2023-10-11 21:01:50 -04:00
bandogora
41abd500f5
Equal number of grid columns and rows (#12180)
Update test snapshots
2023-10-11 21:00:22 -04:00
Robin Malfait
fa9be73c15
Ensure variants with arbitrary values and a modifier are correctly matched in the RegEx based parser (#12179)
* add failing test

* ensure variants can have modifiers in regex

* update changelog
2023-10-11 16:38:30 +02:00
Jordan Pittman
51ac627491 Fix spelling 2023-10-10 16:17:08 -04:00
depfu[bot]
edfb51c5ed Update vite to version 4.4.11 2023-10-10 19:00:57 +00:00
Jordan Pittman
ad66635c15
Fix sorting of utilities that share multiple candidates (#12173)
* Fix sorting of utilities that share multiple candidates

* Update changelog
2023-10-10 13:50:46 -04:00
Robin Malfait
31a80b1794
Reduce quickPluginTest and quickVariantPluginTest snapshot size (#12170)
* sync package-lock.json

* reduce generated examples

By default we use the default config when creating the quick tests for a
given plugin. However, this can result in _a lot_ of changes. For
example, when you update the opacity values, then these values will be
generated for each color, and for each plugin that uses that color.

You can see that behaviour in Adam's PR (https://github.com/tailwindlabs/tailwindcss/pull/11832).
He added 6 values to the opacity configuration. This resulted in a diff
that looked like this:
```
+219,436 −9
```

This reduces the amount of examples for plugins that generate _a lot_ of
output otherwise.

* use `toMatchSnapshot`

This way all the `quickPluginTest` and `quickVariantPluginTest`
implementations are the same. This also allows us to update tests after
config changes in an easier way because they use snapshot tests right
now.

* use real colors for reduced red

This will make it a tiny bit easier to debug what's going on if we need
it in the future since it's using the same values as the real colors.
2023-10-09 16:49:20 +02:00
depfu[bot]
5f00c18bd9 Update rollup to version 3.29.4 2023-10-08 06:20:06 +00:00
depfu[bot]
614c7e2a36 Update browserslist to version 4.22.1 2023-10-04 00:36:35 +00:00
Jordan Pittman
09369d27ec Enable error recovery in Lightning CSS 2023-10-03 13:04:17 -04:00
depfu[bot]
0d205f9376 Update postcss to version 8.4.31 2023-10-03 16:26:45 +00:00
Robin Malfait
82e0acebe0 move failing test to dedicated failing test 2023-10-02 15:12:09 +00:00
Robin Malfait
1879130f45 nit improvement: remove unnecessary spaces 2023-10-02 15:12:09 +00:00
Robin Malfait
9f73b9a33c add missing placeholder-transparent/0 2023-10-02 15:12:09 +00:00
Robin Malfait
a14f6bcf6d update tests to reflect Lightning CSS update
The main things I'm seeing right now are:
1. Some selectors are broken up into multiple selectors, especially when
   there are pseudo classes involved where the output is unknown. For
   example `::ng-deep, ::v-deep {}` will be split into `::ng-deep {},
   ::v-deep {}`
2. It looks like a lot of selectors were dropped, however after looking
   at them in more detail I noticed that they were _duplicated_ before.
   This means that now the output will be smaller in these cases!
2023-10-02 15:12:09 +00:00
Robin Malfait
91fb37cbe3 adjust Lightning CSS API 2023-10-02 15:12:09 +00:00
Robin Malfait
20856edaa1 sync package-lock.json file 2023-10-02 15:12:09 +00:00
depfu[bot]
aa1be48d39 Update lightningcss to version 1.22.0 2023-10-02 15:12:09 +00:00
Robin Malfait
0af88b1494
Improve RegEx parser, reduce possibilities as the key for arbitrary properties (#12121)
* optimize handling of RegEx parser results

Previous:
- Copy `results`, for every subsequent result of other `patterns`
- Loop over results to filter out `undefined` values
- Loop over results to map to `clipAtBalancedParens`

Current:
- For each candidate, push the `clipAtBalancedParens(candidate)` into
  the `results`

This way we are not copying existing results, and we are also avoiding
additional loops over the entire array to filter out `undefined` values
and map to `clipAtBalancedParens`.

* do not allow `]` in the first part of arbitrary properties

```
[foo:bar]
 ─┬─
  └── This part cannot contain `]`
```

This is also a very targeted fix for when the arbitrary properties seem
to match a large piece of text, but shouldn't

* add real world tests for parsing candidate strings

* sync package-lock.json

* update changelog
2023-10-02 16:19:23 +02:00
Jordan Pittman
5542340a75
Eliminate irrelevant rules when applying variants (#12113)
* Eliminate irrelevant rules when applying variants

* Update changelog
2023-09-29 16:31:43 -04:00
Jordan Pittman
457d2b1b1c
Don’t crash when important and parent selectors are equal in @apply (#12112)
* Don’t crash when important and parent selectors are equal in `@apply`

* Update changelog
2023-09-29 16:04:32 -04:00
Jordan Pittman
d32228628e
Update types to work with Node16 module resolution (#12097)
* Update types to work with Node16 module resolution

* Update changelog
2023-09-28 11:12:50 -04:00
Jordan Pittman
1e7dd3a0c4
Skip over classes inside :not(…) when nested in an at-rule (#12105)
* Skip over classes inside `:not(…)` when nested in an at-rule

When defining a utility we skip over classes inside `:not(…)` but we missed doing this when classes were contained within an at-rule. This fixes that.

* Update changelog
2023-09-28 10:45:59 -04:00
depfu[bot]
61dc99f4ad Update postcss to version 8.4.30 2023-09-26 22:37:03 +00:00
Jordan Pittman
05d0a6f8d2
Support loading plugins by package / file name (#12087)
* Cleanup test

Strings are going to actually do something now

* Refactor plugin loading in `resolveConfig`

This should keep the same instance of a plugin in use too. Maybe even reduce memory usage?! idk we’ll see

* Support simple plugin configs

* Update types

* Update changelog
2023-09-26 13:21:42 -04:00
Jordan Pittman
aaca7c4c4c
Batch reading content files to prevent too many open files error (#12079)
* Refactor

* Refactor

* Batch content file reads in Node into groups of 500

We shouldn’t need to do this for our Rust code because it utilizes Rayon’s default thread pool for parallelism. This threadpool has roughly the number of cores as the number of available threads except when overridden. This generally is much, much lower than 500 and can be explicitly overridden via an env var to work around potential issues with open file descriptors if anyone ever runs into that.

* Fix sequential/parallel flip

* Update changelog
2023-09-25 12:18:11 -04:00
Robin Malfait
64c7d0efe7
update package-lock.json 2023-09-25 13:47:45 +02:00
depfu[bot]
b33c30284f Update eslint to version 8.49.0 2023-09-22 21:35:46 +00:00
depfu[bot]
2e031337ce Update turbo to version 1.10.14 2023-09-22 20:34:49 +00:00
depfu[bot]
0e7f936995 Update resolve to version 1.22.6 2023-09-22 00:07:11 +00:00
depfu[bot]
3e16028406 Update all of jest to version 29.7.0 2023-09-21 13:22:32 +00:00
depfu[bot]
bd970d03f3 Update sass to version 1.67.0 2023-09-21 01:59:04 +00:00
Jordan Pittman
d3f22216c1
Handle variable colors that have variable fallback values (#12049)
* Parse colors even when variable has fallback that is a variable

* Update changelog
2023-09-20 16:34:39 -04:00
Yuhei Yasuda
5366d242c2
Add support for text-wrap: pretty (#12031)
* Add support for `text-wrap: pretty`

* Update changelog

---------

Co-authored-by: Jonathan Reinink <jonathan@reinink.ca>
2023-09-19 08:49:20 -04:00
depfu[bot]
42e75ba4b8 Update jiti to version 1.20.0 2023-09-15 14:37:13 +00:00
depfu[bot]
91e91e067d Update rollup to version 3.29.1 2023-09-14 07:47:42 +00:00
depfu[bot]
0bb275797f Update @swc/core to version 1.3.84 2023-09-14 03:53:10 +00:00
depfu[bot]
87d7b91ac7 Update postcss to version 8.4.29 2023-09-12 22:22:06 +00:00
Brandon McConnell
9faf10958b
Update Preflight html styles to include shadow DOM :host pseudo-class (#11200)
* Extend current preflight `html` styles to support other root/host scopes

Supports SVG root scope, ShadowDOM, and presumably other root scopes

* Replace `:root` with `html`

* Update tests

* Update changelog

---------

Co-authored-by: Jonathan Reinink <jonathan@reinink.ca>
2023-09-08 11:46:57 -04:00
Viktor Szépe
7a86f3ae23
Remove matrix.setup release-insiders.yml (#11954) 2023-09-05 19:47:03 -04:00
Robin Malfait
696810fd5a update tests to reflect changes in Lightning CSS
This commit (284debfb49) introduced:

"Don't put unknown pseudo elements in an `:is()`"

I thought this was a bug at first, but looking at MDN, these pseudo
elements are non-standard:

- https://developer.mozilla.org/en-US/docs/Web/CSS/::-webkit-inner-spin-button
- https://developer.mozilla.org/en-US/docs/Web/CSS/::-webkit-outer-spin-button
2023-08-28 09:53:46 +00:00
Robin Malfait
42e168b35c sync package-lock.json 2023-08-28 09:53:46 +00:00
depfu[bot]
63252a57e4 Update lightningcss to version 1.21.7 2023-08-28 09:53:46 +00:00
depfu[bot]
8f1987567b Update sass to version 1.66.1 2023-08-25 23:29:56 +00:00
depfu[bot]
68260bd9e2 Update jiti to version 1.19.3 2023-08-25 14:34:20 +00:00
depfu[bot]
73272faaa8 Update postcss to version 8.4.28 2023-08-23 14:02:19 +00:00
depfu[bot]
8b4862b7c7 Update @napi-rs/cli to version 2.16.3 2023-08-23 14:01:32 +00:00
Jordan Pittman
0198e84c90 Remove unused imports 2023-08-23 09:42:07 -04:00
Jordan Pittman
9a52f90fe9 Remove grouping prototype
This code has been sitting around for a while disabled and untested. And it does not work with the Oxide parser. So we’re gonna remove it.
2023-08-21 11:01:19 -04:00
Jordan Pittman
9fcfea47a6 Sync changelog with v3.3 branch 2023-08-21 10:52:04 -04:00
depfu[bot]
28e8fdf24c Update @swc/jest to version 0.2.29 2023-08-19 05:35:05 +00:00
depfu[bot]
a2fa693276 Update eslint to version 8.47.0 2023-08-18 17:06:36 +00:00
depfu[bot]
d58d5b2797
Update eslint-config-prettier to version 9.0.0 (#11815)
Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2023-08-17 16:58:51 -04:00
Adam Wathan
00e73b0881
Extend opacity scale to include all steps of 5 (#11832)
* Extend opacity scale to include all steps of 5

* Update changelog

---------

Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
2023-08-16 11:44:14 -04:00
Jordan Pittman
4f404ff15f Set the default rust toolchain for musl linux installs 2023-08-14 16:50:46 -04:00
Jonathan Reinink
9c828ff898
Remove Rust installation from CI (#11826) 2023-08-14 16:09:24 -04:00
depfu[bot]
12b670cc6b Update vite to version 4.4.9 2023-08-14 09:43:00 +00:00
Viktor Szépe
77ae72402e
Fix typos (#11805) 2023-08-12 08:17:41 -04:00
depfu[bot]
4c34b2ed34 Update resolve to version 1.22.4 2023-08-12 00:36:22 +00:00
depfu[bot]
ff26372570 Update rollup to version 3.28.0 2023-08-11 13:59:49 +00:00
Robin Malfait
237e71598d update .swcrc configuration
The `env` and `jsc.target` combination isn't valid.
Removing `env` works, but it introduces a lot of additional code that
wasn't there before.
I started by using es2015, es2016 and last but not least es2018. es2018
produces the most similar code compared to before. The main differences
are:
- Single vs double qoutes
- Now we will have `catch (e) {}` instead of the more modern `catch {}`
2023-08-10 11:02:05 +00:00
Robin Malfait
95a4fcd1a9 sync package-lock.json 2023-08-10 11:02:05 +00:00
depfu[bot]
739df89264 Update @swc/core to version 1.3.75 2023-08-10 11:02:05 +00:00
Ryan Leichty
922e7d9407
Correct formatting for inconsistent comments (#11777)
* Correct formatting for inconsistent comments

Updated the formatting for several comments to be consistent with the rest of the document.

I was incorporating the preflight in a non-Tailwind project and noticed the difference in comment styles.

* add missing period

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2023-08-09 13:19:12 +02:00
Emilia
4f0bb0b73a
feat(preflight): simplify sans-serif font stack (#11748)
* feat(preflight): simplify sans-serif font stack

`-apple-system` and `BlinkMacSystemFont` were historically needed for
IE11 and chakra-based Edge (The one that wasn't chromium-based).

https://caniuse.com/font-family-system-ui has more details around it.

* further simplify `font-family`

* update tests

* update changelog

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2023-08-08 12:26:30 +02:00
depfu[bot]
05e307bb6c Update browserslist to version 4.21.10 2023-08-06 22:20:15 +00:00
depfu[bot]
26c4cb3b5c Update eslint-config-prettier to version 8.10.0 2023-08-04 08:55:18 +00:00
depfu[bot]
778bea7567 Update all of jest to version 29.6.2 2023-08-03 16:04:35 +00:00
depfu[bot]
cfc853b741 Update postcss to version 8.4.27 2023-08-03 14:03:56 +00:00
depfu[bot]
eeec89a07a Update turbo to version 1.10.12 2023-08-01 23:03:10 +00:00
wimbarelds
0f0d06074c
Make content optional for presets in TypeScript types (#11730)
* Update config.d.ts, Make array members partial in Config

Instead of `Partial<Array<Thing>>` have `Array<Partial<Thing>>`

* simplify types further

* update changelog

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2023-08-01 16:19:21 +02:00
Jordan Pittman
a93c61f635 Fix grouping prototype 2023-07-31 12:59:18 -04:00
Jordan Pittman
703a8b014d Update tests 2023-07-31 12:57:46 -04:00
depfu[bot]
86fb660593 Update fast-glob to version 3.3.1 2023-07-29 09:02:32 +00:00
Robin Malfait
e4b398b24f
Fix incorrectly generated CSS when using square brackets inside arbitrary properties (#11709)
* ensure nested square brackets are handled properly inside arbitrary properties

* update changelog
2023-07-28 12:37:43 +02:00
depfu[bot]
5e24f8e08a Update sass to version 1.64.1 2023-07-27 22:58:48 +00:00
Robin Malfait
a72d97cebb
Skip calc() normalisation in nested theme() calls (#11705)
* add `calc` normalisation test cases using `theme()`

* ignore formatting in some known functions, such as `theme`

* update changelog
2023-07-27 17:50:58 +02:00
Robin Malfait
ae4d21308d
Improve normalisation of calc()-like functions (#11686)
* parse the `calc()`-like expressions and format them

* update changelog

* Add test case for double negatives

wanted to be sure this worked

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2023-07-26 16:26:13 +02:00
Robin Malfait
e0c52a9332
sync package-lock.json 2023-07-25 13:23:27 +02:00
depfu[bot]
757879442f Update @napi-rs/cli to version 2.16.2 2023-07-24 15:54:15 +00:00
Balázs Orbán
2a58e7c431
chore: add GitHub Action to manage +1 comments (#11645) 2023-07-24 11:57:35 +02:00
Robin Malfait
dba5043591
Ensure all built-in plugins have tests (#11618)
* move `preflight` tests to `plugins` test folder

* move `container` tests to `plugins` test folder

* add a test that ensures that all built-in plugins have tests

This is based on a few assumptions:

1. All plugins exist in the `corePlugins.js` file and are exported as
   `corePlugins`
2. Each exported plugin in that object has a file with a corresponding
   name in the current (`tests/plugins/`) folder

* add dedicated plugin tests in the `plugins` folder

* add a test that ensures that all built-in variant plugins have tests

This is based on a few assumptions:

1. All variant plugins exist in the `corePlugins.js` file and are
   exported as `variantPlugins`
2. Each exported plugin in that object has a file with a corresponding
   name in the current (`tests/plugins/variants/`) folder

* add dedicated variant plugin tests in the `plugins/variants` folder

* cleanup basic usage test

* move existing animation tests to animation plugin tests

* move `apply-important-selector` tests to more appropriate location
2023-07-24 11:41:52 +02:00
depfu[bot]
6eed8cb3f6 Update sucrase to version 3.34.0 2023-07-22 00:34:40 +00:00
depfu[bot]
6e8c5c9868 Update eslint to version 8.45.0 2023-07-21 18:04:16 +00:00
depfu[bot]
88316a18e5 Update vite to version 4.4.4 2023-07-18 17:02:16 +00:00
Robin Malfait
7f31ac15ff
ensure we await the processTailwindFeatures when we have multiple roots 2023-07-13 17:20:46 +02:00
depfu[bot]
adadf807fa Update postcss to version 8.4.25 2023-07-13 14:01:01 +00:00
depfu[bot]
3664fd5e02 Update rollup to version 3.26.2 2023-07-13 13:43:32 +00:00
depfu[bot]
ceba1321ec Update sass to version 1.63.6 2023-07-12 02:28:39 +00:00
Jordan Pittman
516c1498c7
Allow variant to be an at-rule without a prelude (#11589)
* Allow variant to be an at-rule without a prelude

* Update changelog
2023-07-11 16:04:38 -04:00
depfu[bot]
bc1af02a56 Update all of jest to version 29.6.1 2023-07-11 17:19:31 +00:00
Robin Malfait
b6b3f854a7
fix experimental feature flag resolution for 'all'
When you have a custom config for the prefix/separator values, then we
want to disable the `oxideParser` for now. But we also have to ensure
that if you use the value `"all"` for the experimental feature flag that
we enable all feature flags.

We were using the keys of the array instead of the values.
2023-07-10 16:09:49 +02:00
depfu[bot]
3779be2bab Update @swc/core to version 1.3.68 2023-07-09 11:30:36 +00:00
depfu[bot]
adbc7593dc Update turbo to version 1.10.7 2023-07-09 11:00:43 +00:00
depfu[bot]
2bfdbce964 Update parcel to version 2.9.3 2023-07-06 22:14:53 +00:00
Robin Malfait
01d1b9a607
Make PostCSS plugin async to improve performance (#11548)
* make main plugin async

This way we can improve the `fs.readFileSync` to a bunch of
`fs.promises.readFile` in a `Promise.all` instead.

* make CLI plugin async

* update CHANGELOG
2023-07-05 16:19:03 +02:00
Robin Malfait
b57f4d74e2
Bump jiti, lightningcss, fast-glob and browserlist dependencies and reflect lightningcss related improvements in tests (#11550)
* bump dependencies

* reflect lightningcss related changes

* update changelog
2023-07-05 15:03:46 +02:00
Jordan Pittman
37575ea0bd Update changelog 2023-06-29 13:36:34 -04:00
Jordan Pittman
40cb82ce75
Sort classes using position of first matching rule (#11504)
* Refactor

* Sort based on first occurence of a candidate

This primarily affects components and utilities which contain multiple matched classes

* Simplify

* Update changelog

* Update
2023-06-29 09:49:53 -04:00
Jordan Pittman
615c157a27
Don't prefix arbitrary classes in peer/group variants (#11454)
* Refactor

* Don’t prefix classes in arbitrary values for group and peer

* use `foo` instead of `lol`

* handle the prefix inside the group/peer variants

Then add the `NoPrefix` feature to the variant itself, which will skip
prefixing any other class in the generated selector (because we already
took care of prefixing `.group` and `.peer`).

We are using an internal symbol such that:

- We can keep it as a private API
- We don't introduce a breaking change

* refactor to simple object instead

We will still use a symbol as an internal/private marker, but the data
itself will be a simple object for now.

If we want to refactor this (and more) in the future using bitflags then
we can refactor that in a separate PR.

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2023-06-28 12:37:52 -04:00
A Dev $el
6722e78b35
Ensure CI jobs don't run for longer than 15 minutes (#11496)
* fix: prevent long unwanted runs!

* add timeout-minutes to all the workflows

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2023-06-27 23:11:57 +02:00
depfu[bot]
e4af07f7ad Update browserslist to version 4.21.9 2023-06-26 07:01:20 +00:00
depfu[bot]
c6370e15f9 Update parcel to version 2.9.2 2023-06-25 11:28:59 +00:00
Robin Malfait
9f591a5586
Always prepend the license comment (#11476)
* always prepend the Tailwind License

Even when you are not using preflight.

* drop the Tailwind License from the preflight plugin

* drop the license in the tests

This allows us to focus on the actual generated CSS without the license.
The license also includes the version number which we would have to
update every time we release a new version.

* update the source maps snapshots

* always prepend the license when using the CLI

* update Tailwind CLI integration test
2023-06-22 16:41:37 -04:00
Jordan Pittman
a2196b06ab
Escape animation names when prefixes contain special characters (#11470)
* Escape animation names when prefixes contain special characters

* Update changelog
2023-06-21 10:13:19 -04:00
depfu[bot]
eae2b7a3f4 Update turbo to version 1.10.3 2023-06-20 21:32:30 +00:00
depfu[bot]
776d5bf614 Update rollup to version 3.25.1 2023-06-19 06:30:22 +00:00
depfu[bot]
ac6e8733e9 Update eslint to version 8.42.0 2023-06-16 21:17:40 +00:00
depfu[bot]
a7a1f9347c Update sass to version 1.63.4 2023-06-16 17:58:29 +00:00
Robin Malfait
86f9c6f092
Simplify browserslist setup by relying on defaults (#11412)
* use `defaults` for `browserslist`

+ some features that we require for Tailwind to work.

* simplify Lightning CSS features

Always transpile `Nesting`, never transpile `LogicalProperties`.

* bump caniuse-lite (`npx browserslist@latest --update-db`)

* reflect changes in the tests

* update changelog
2023-06-12 22:07:06 +02:00
cunzaizhuyi
3be258c218
chore: make sentence smooth (#11407)
* chore: make sentence smooth

* chore: everything typo

* chore: its

* feat: e.g.

* chore: a => an; remove repeated `to`

* Update offsets.js

* Update generateRules.js

---------

Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2023-06-11 11:06:03 -04:00
Robin Malfait
ef2ebb28d5
Explicitly configure Lightning CSS features, and prefer user browserslist over default browserslist (#11402)
* exclude `Features.LogicalProperties` lightningcss feature

We don't want this to automatically apply since we have dedicated
logical property based utilities already.

We also have a feature flag to toggle some utilities to make use of
logical property based ones.

This feature should be opt-in to prevent backwards compatibility (e.g.:
RTL sites that _did_ use a mr-4 explicitly).

* update default browserslist

* explicitly include/exclude Lightning CSS features

* update tests based on new browserslist and include/exclude features from Lightning CSS

* update integration tests

* prefer user browserslist, fallback to built-in browserslist

* always include Nesting

If a custom browserslist config is used, then we don't explicitly set
the include/exclude features from Lightning CSS (except for nesting, we
always want to process nesting)

* ensure to fallback to the current working directory

* update changelog
2023-06-09 20:37:36 +02:00
Robin Malfait
ea3d9cd57a
compare the correct values in Jest tests 2023-06-09 16:04:24 +02:00
Jordan Pittman
e51b09ca65
Move unknown pseudo elements outside of :is (#11345)
* More pseudo elements outside of `:is` by default

* Update changelog
2023-06-08 15:00:58 -04:00
Robin Malfait
eb8d9294c5
Make the Rust based parser the default (#11394)
* add failling test using array syntax without space delimiter

* add Rust test with candidates in array without spaces

* Fix JS arrays without spaces

* make the `oxideParser` the default

* sync feature flags with reality

* use better example in tests to be more real

* skip failing tests in the RegEx parser

* update changelog

* make `clippy` happy

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2023-06-08 14:13:21 +02:00
Robin Malfait
55daf8e0f5
Ensure the oxide parser has feature parity with the stable RegEx parser (#11389)
* WIP

* use `parse` instead of `defaultExtractor`

* skip `Vue` describe block

* add a few more dedicated arbitrary values/properties tests

* use parallel parsing

* splitup Vue tests

* add some Rust specific tests

* setup parse candidate strings test system

These tests will run against the `Regex` and `Rust` based parsers. We
have groups of classes of various shapes and forms + variants and
rendered in various template situation (plain, html, Vue, ...)

+ enable all skipped tests

* ensure we also validate the classes with variants

The classes with variants are built in the `templateTable` function, so
we get them out again by using the potional arguments of the `test.each`
cb function.

* cleanup test suite

* add "anti-test" tests

To make sure that we are _not_ parsing out certain values given a
certain input.

* Add ParseAction enum

* Restart parsing following an arbitrary parse failure

* Split variants off before validating the uility part

* Collapse candidate from the end when validation fails

* Support `<`, and `>` in variant position

* fix error

* format parser.rs

* Refactor

* Update editorconfig

* wip

* wip

* Refactor

* Refactor

* Simplify

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* run `cargo clippy --fix`

* run `cargo fmt`

* implement `cargo clippy` suggestions

These were not applied using `cargo clippy --fix`

* only allow `.` in the candidate part when surrounded by 0-9

This is only in the candidate part, not the arbitrary part.

* % characters can only appear at the end after digits

* > and < should only be part of variants (start OR end)

It can technically be inside the candidate when we have stacked
variants:
```
dark:<sm:underline
dark:md>:underline
```

* handle parsing utilities within quotes, parans or brackets

* mark `pt-1.5` as an expected value sliced out from `["pt-1.5"]`

* Add cursor abstraction

* wip

* disable the oxideParser if using a custom `prefix` or `separator`

* update tests

* Use cursor abstraction

* Refactor more code toward use of global cursor

* wip

* simplify

* Simplify

* Simplify

* Simplify

* Cleanup

* wip

* Simplify

* wip

* Simplify

* Handle candidates ending with % sign

* Tweak code a bit

* fmt

* Simplify

* Add cursor details to trace

* cargo fmt

* use preferred `zoom-0.5` name instead of `zoom-.5`

* drop over-extracted utilities in oxide parser

The RegEx parser does extract `underline` from

```html
<div class="peer-aria-[labelledby='a_b']:underline"></div>
```
... but that's not needed and is not happening in the oxide parser

This means that we have to make the output check a little bit different
but they are explicit based on the feature flag.

* allow extracting variants+utilities inside `{}` for the oxide parser

* characters in candidates such as `group-${id}` should not be allowed

* do not extract any of the following candidate `w-[foo-bar]w-[bar-baz]`

* ensure we can consume the full candidate and discard it

* Add fast skipping of whitespace

* Use fast skipping whenever possible

* Add fast skipping to benchmark

* Hand-tune to generate more optimized assembly

* Move code around a bit

This makes sure all the fancy SIMD stuff is as early as possible. This results in an extremely minor perf increase.

* Undo tweak

no meaningful perf difference in real world scenarios

* Disable fast skipping for now

It needs to be done in a different spot so it doesn’t affect how things are returned

* Change test names

* Fix normalize config error

* cleanup a bit

* Cleanup

* Extract validation result enum

* Cleanup comments

* Simplify

* Fix formatting

* Run clippy

* wip

* add `md>` under the special characters test set

---------

Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2023-06-07 17:44:35 +02:00
Robin Malfait
e572dc645a
Bump lightningcss dependency (#11388)
* bump `lightningcss`

* allow for non-standard selector combinators such as `::deep`

* just use Prettier when comparing CSS results

We were using 2 different engines where the stable one was not using
Lightning CSS and the Oxide one was using Lightning CSS.

To ensure that we didn't have to rewrite every single test expectation,
the `toMatchFormattedCss` parsed both the actual and expected value
using Lightning CSS (to make the result similar), then it used Prettier
to make it... pretty.

Right now we _only_ use Lightning CSS, which means that we can drop the
additional lightningcss format step and just use Prettier on both the
actual and expected values.

Pretty also only prettifies the CSS, it doesn't rewrite it. E.g.:
`@media (min-width: 768px)` will not be optimized to `@media (width >=
768px)`, that's something that Lightning CSS does for us.

This will require some changes in our test output, but it will be
consistent afterwards because there won't be hidden transformation steps
anymore. Because up until now it could be that the actual result was
`color: black` but the tests showed `color: #000` (because it is
shorter).

This change will reflect reality.

* update tests based on previous commit

* only use `toMatchFormattedCss` instead of `toMatchCss`

They both do the exact same thing right now. While `toMatchCss` is
shorter, `toMatchFormattedCss` makes a bit more sense since we are
comparing the Prettier results.

* update integration tests
2023-06-07 15:56:32 +02:00
depfu[bot]
571c27e1f1 Update @swc/core to version 1.3.62 2023-06-06 08:00:26 +00:00
depfu[bot]
fd74f04437 Update vite to version 4.3.9 2023-06-06 04:12:11 +00:00
depfu[bot]
f661c0371c Update browserslist to version 4.21.7 2023-06-04 10:31:31 +00:00
depfu[bot]
729dafeb82 Update postcss to version 8.4.24 2023-06-04 10:00:20 +00:00
Robin Malfait
b05b617601
Bump Rust version to 1.70.0 (#11344)
* `cargo clippy --fix`

* use `map_while` instead of `filter_map`

- `filter_map()` will run forever if the iterator repeatedly produces an `Err`

```
❯ cargo clippy
    Checking tailwindcss-oxide v0.1.0 (/Users/robin/github.com/tailwindlabs/tailwindcss/oxide/crates/cli)
warning: `filter_map()` will run forever if the iterator repeatedly produces an `Err`
   --> crates/cli/src/main.rs:108:14
    |
108 |             .filter_map(Result::ok)
    |              ^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `map_while(Result::ok)`
    |
note: this expression returning a `std::io::Lines` may produce an infinite number of `Err` in case of a read error
   --> crates/cli/src/main.rs:106:9
    |
106 | /         read_lines(path)
107 | |             .unwrap()
    | |_____________________^
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#lines_filter_map_ok
    = note: `#[warn(clippy::lines_filter_map_ok)]` on by default

warning: `tailwindcss-oxide` (bin "tailwindcss-oxide") generated 1 warning
    Finished dev [unoptimized + debuginfo] target(s) in 0.13s
```

* log used versions of all the tools we are using

* ensure we install Rust
2023-06-02 15:24:40 +02:00
depfu[bot]
9794b83985 Update parcel to version 2.9.1 2023-06-02 08:44:39 +00:00
Robin Malfait
5ddd9c4db6
Fallback to RegEx based parser when using custom transformers or extractors (#11335)
* handle custom `transformer` and `extractor` implementations

Right now the Rust based parser can't work with custom `transfomers` or
`extractors`.

In a perfect world we can implement as many custom parsers/extractors in
Rust such that we don't need this at all. In an almost perfect world we
can pass the transformer and extractor to the Rust based parser and call
the callback functions to handle all of this. This is probably what we
are going to do in the future but this requires more work to make sure
that:

1. It works as expected
2. Doesn't result in (major) performance issues

Since it currently doesn't work with the Rust based parser, we can
implement a fix for this in the meantime before we reach the "perfect"
solution.

One solution to this problem is to check if we do have a custom
transformer or a custom extractor and if we do, then we can bail on the
Rust parser completely and just use the current regex based parser.

An alternative solution, the solution implemented here, is that we group
the `changedContent` into 2 buckets. The bucket where we rely on the
default transformer and extractor and a bucket where a custom
transformer or extractor is used.

Then, the bucket where we use the default transformer and extractor can
still rely on the way faster Rust based parser. For the other bucket we
fallback to the regex based parser.

The nice part about this is that we can use both parsers at the same
time, and the majority of the use cases should use the faster Rust based
parser.

* update changelog
2023-06-01 22:37:12 +02:00
Robin Malfait
5c7a8e98ea
use parseCandidateStrings directly (#11332)
+ drop the `parseCandidateStringsFromFiles` since this can be
  implemented using `parseCandidateStrings` already.
2023-06-01 13:24:53 +02:00
Robin Malfait
352c563d99
Fix source maps issue resulting in a crash (#11319)
* only try to pretty print `err.source` if it exists

* fix small typo

* drop unnecessary path resolving

* add `from` and `to` to `run` test helper

* enable all source map tests

* ensure source maps are generated properly

* update annotation values in tests

* handle `map: true`

* add test for `map: { inline: true }`

* force Lightning CSS to handle an inline source map

* base64 encode the test name

This will get rid of special characters that can cause issues /
confusion when debugging tests.

* ensure utilities and components with variants are pointing to the correct source

Co-authored-by: Jordan Pittman <jordan@cryptica.me>

* update changelog

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2023-05-31 18:28:11 +02:00
Robin Malfait
ecebde1d52
bump napi-rs in Cargo.toml 2023-05-31 15:31:38 +02:00
Robin Malfait
c9138b2e0b
bump napi-rs (#11328) 2023-05-31 13:44:28 +02:00
Adam Wathan
cb1a0e1a95
Add support for text-wrap property (#11320)
Update changelog

Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
2023-05-30 15:26:27 -04:00
Adam Wathan
e1d4955431 Move entry in changelog 2023-05-30 12:21:51 -04:00
Adam Wathan
69d86873c3
Add has-* variants for :has(...) pseudo-class (#11318)
* Add `has-*` variants for `:has(...)` pseudo-class

* Update changelog

* Fix mistake in test

---------

Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
2023-05-30 12:21:08 -04:00
Adam Wathan
7248f2121e
Add svh, lvh, and dvh values to default theme (#11317)
* Add svh, lvh, and dvh values to default theme

* Update changelog

---------

Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
2023-05-30 11:58:16 -04:00
Robin Malfait
820815be0b
Remove autoprefixer dependency (#11315)
* remove `autoprefixer` dependency

This was only used in the CLI and we are not using it anymore. The
functionality of `autoprefixer` is baked into `lightningcss`.

* update changelog
2023-05-30 13:27:15 +02:00
depfu[bot]
8719be3944 Update rollup to version 3.23.0 2023-05-29 06:30:31 +00:00
Adam Wathan
fd500fcd0c Exclude variantPlugins from corePluginList
This change avoids unnecessarily registering variantPlugins twice.
2023-05-26 21:10:02 -04:00
Adam Wathan
0917fcaa33 Don't differentiate between oxide/stable engines in CHANGELOG 2023-05-25 10:12:18 -04:00
Robin Malfait
04c4de1997
Deprecate --no-autoprefixer flag in the CLI (#11280)
* enable `--no-autoprefixer` test

* handle `--no-autoprefixer` using lightningcss

* bump default chrome version for lightningcss plugin

* handle `map` being nullable

* add `--no-autoprefixer` test including nesting

* set Chrome to version 111 as a default value

* resolve the output message when resolving the promise

* deprecate `--no-autoprefixer` for the CLI

* update changelog
2023-05-25 16:06:00 +02:00
Robin Malfait
88ab01c7f8
update build-linux steps in release workflows 2023-05-25 12:15:55 +02:00
Robin Malfait
49400f90f1
fix CI badge in README 2023-05-25 12:01:03 +02:00
Robin Malfait
581cf6abcc
Merge engines (#11275)
* Simplify CI, make oxide engine the default (#11281)

* remove integration tests for the `stable` engine

* make `integration-tests-oxide` the default integration tests workflow

* remove unnecessary CACHE_PREFIX

* drop `ci-stable.yml` workflow

* make `CI — Oxide` the default

+ drop testing against 3.3 branch (since this is stable only)
+ remove unnecessary CACHE_PREFIX

* drop `release-insiders-stable.yml` workflow

* make `Release Insiders — Oxide` the default

+ change release channel to just `insiders` instead of `oxide-insders`

* drop 3.3 branch from integration tests

* change job name for insiders release

This makes it consistent with the other job names

* prep `release-oxide.yml` workflow to be the default workflow

* add Tailwind Play update

Currently commented out until we figure out how this will work exactly.

* use `env.VERSION` for the version we want to release

* drop `release-stable.yml` workflow

* make `release-oxide.yml` the default `release.yml` workflow

* inline "Calculate environment variables" step

* cache node_modules and cargo related files/folders in the release step

* always use the default CLI

* drop oxide specific implementation

* ensure we test `--postcss` in the CLI

Even for the Oxide engine

* always process `@import` rules

* remove `generalizedModifiers` flag

This was already enabled by default. This commits removes some of the
outstanding references to it.

* remove built-in peer dependencies for the CLI

* drop unused esbuild dependency

* fix type for `configBag`

* setup Lightning CSS for the CLI

* drop `cssnano` dependency

We only used this in the CLI and we now use Lighting CSS which has this
built in already. Therefore we can get rid of this as a dependency.

* ensure `imported.css` file exists in integration tests

* passthrough `options` to lightningcss

Ideally we can read this from `result.map` instead. However this doesn't
get filled in for whatever reason even though we use `map: {inline:true}`.

You can run the `tests/cli.test.js`, and more specifically the
`--postcss supports process options with custom config` test.

* Always process CSS with Lightning CSS

Some tests temporarily skipped. Autoprefixer removed from `postcss.config.js` stubs since vendor prefixing handled by Lightning.

* Remove code for normalizing `@import "tailwindcss/*"` statements

* ensure tailwind doesn't crash when a `tailwind.config.js` file is not present

* add `log.group` to group multiple messages together with different types

* remove engine specific checks in integration tests

* remove unused imports/variables

Make linting check happy

* support `content: ['auto']`

This will allow us to still use auto content detection, but since this
is part of the `content` array, it also allows you to add custom paths
if you want.

E.g.:
```js
content: ['auto', './node_modules/my-library/*.{jsx,tsx}']
```

* ensure `content.files` is always an array

This allows us to simplify some checks because 'auto' will now be part
of the array instead of checking if `content.files` is auto or if it is
an array containing 'auto'.

* fix tests, ensure default config is not injected

* simplify `auto` normalization

* always use the defaultFullConfig

No need to override the defaultFullConfig with `{content: 'auto'}`
because this will be included in the default config already.

* drop condition which ensured `files` was an empty array

This is not needed anymore because when `content` is omitted it behaves
as if it was set to `auto`.

* drop removal of `content`

We used to remove the `content` from the actual configuration files when
using `tailwindcss init` for the oxide engine. This was to ensure that
`auto content detection` was used instead (by default).

But now it is always enabled by default therefore this is not needed
anymore.

* ensure setting `content: 'auto'` works

* improve file cache handling

We have to make sure that we get into the previous state whenver a test
is done.
We were using some `!fileCache[filePath]` checks, however we also used
`null` as a sentinel value when something wasn't found. But, `null` is
falsey as well so some checks where incorrect.

Using a dedicated value and a `Map` makes this safer and more correct
because we can now use `.has()`.

* drop `cleanupFile`, `removeFile` will already take care of it

* drop oxide check in `resolvedChangedContent`

At this point the `context.tailwindConfig.content.files` is already
fully resolved regardless of the engine we are in.

* refactor `parseCandidateStringsFromFiles` strategy, to make use of a proper feature flag

* use feature flag for sorting classes

Once the Oxide engine is the default default, then we can drop this
entirely because the result from the Rust parser will already be sorted.

* drop `crosscheck` from tests where it is safe

First pass of deleting `crosscheck`. These are all the places where we
didn't have any differences between the Oxide and Stable engine.

* replace `__OXIDE__` in corePlugins with feature flag

* use `globalThis.__OXIDE__ ?? false`

This will ensure that in places where we don't have `__OXIDE__` that we
can still properly fallback to `false`.

* use `flex` as the go-to utility when testing features

* prefer `space-utilities` tests instead of `oxide` test

* drop crosscheck check

Since the `oxideParser` is disabled by default, all the tests can use
the `stable` output.

* use simple `false` default values for feature flag defaults

* drop `__OXIDE__` injections in build scripts

* use stable `flex` and `z-{...}` utilities instead of color utilities

flex and z-index related utilities are more stable than the color
utilities right now because the color utilities will use a raw color
value or a combination with a css variable depending on a feature flag.

* drop unused `ENGINE` environment variable

* drop stable engine manifest files

* drop `swap-engines` tooling

---------

Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
2023-05-24 22:10:00 -04:00
Daniel Kouznetsov
0af64ee3e8
fixed a type in generateRules.js (#11264) 2023-05-22 08:34:10 -04:00
Robin Malfait
9ebaaaccb3
Explicitly list published CSS files (#11260)
* explicitly list all css files

This also omits the oxide engine only `index.css`

* update changelog
2023-05-19 18:13:02 +02:00
Robin Malfait
f49b054b7d
[Oxide] Add built-in @import processing support (#11239)
* run PostCSS relative to test file

The PostCSS `from` was always set to the `tests/util/run` file with the
current test name as a query param to make it unique. However, that
results in issues with relative file paths because they are now relative
to this file instead of the actual test file.

Luckily, `expect.getState()` exposes the `testPath` value that points to
the current test file itself.

* add built in `@import` processing

+ additional features such that you can use `@tailwind` and `@config`
  directives before the `@import` at rule which isn't normally allowed.
  However "@tailwind" in a way is an `@import` and `@config` in a way is
  a `@charset` which can be used before the `@import` at rule.

* drop partial normalizeTailwindDirectives when in the Oxide engine

This will now be handled by Tailwind from a higher level, so the core
doesn't have to worry about normalizing this anymore since it will
always be normalized right now.

* add `handleImportAtRules` to CLI

* update changelog

* scope `import processing` to the oxide engine

* update Changelog

* use direct comparison

We don't need the `startsWith` check anymore because the contents of the
comment will be the actual value we expect.
2023-05-15 19:29:57 +02:00
Robin Malfait
0e171fd2dd
[Oxide] Ensure generated tailwind.config.js file is formatted properly (#11221)
* ensure beginning `\n` is not removed

The `\s` regex includes newlines `\n`, but we only want leading spaces
to be removed.

* update changelog
2023-05-13 00:15:30 +02:00
Robin Malfait
764f6a19d4
add missing ^ to package-lock.json 2023-05-12 19:35:23 +02:00
Robin Malfait
2f21b22049
update CHANGELOG 2023-05-12 18:58:21 +02:00
Adam Wathan
dce8280d76
[Oxide] Automatic content detection (#11173)
* resolve all _existing_ content paths

* pin `@napi-rs/cli`

* WIP: Log all resolved content files/globs

* only filter out raw changed content in non-auto mode

* skip parseCandidateFiles cache in `auto` mode

* improve algorithm of detecting content paths

1. Files in the root should be listed statically instead of using globs.
2. Files and folders in special known direct child folders should be
   listed statically instead of using globs (e.g.: `public`). This is
   because these special folders are often used to store generated AND
   source files at the same time. Using globs could trigger infinite
   loops because we are watching and acting upon dist files.
3. All file extensions found in the project, should be used in the globs
   in addition to a known set of extensions.
4. Direct folders seen from the root, can use the glob syntax
   `<root>/src/**/*.{...known-extensions}`

* inline wanted-extensions

Not 100% convinced yet, but seems cleaner so far.

* ensure writing an file also makes the parent folder(s)

* add integration tests for the auto content feature

* add pnpm and bun lock files

* Revert "inline wanted-extensions"

This reverts commit 879c1248524e84216125f4a24e0160b40736333a.

* sort binary-extensions and add lockb

* sort + add `lock` to ignored extensions

* drop `yarn.lock`, because lock extensions are already covered

* group template extensions

This will make it a bit easier to organize in the future.

* drop empty lines and commented lines from template-extensions

* skip the config path when resolving template files

The config file will automatically trigger a rebuild when this file is
changed. However, this should not be part of the template files because
that could cause additional css that's not being used.

* make `auto content` the default in the oxide engine

- In the oxide engine, the default `content: []` will be dropped from
  the default configuration (config.simple.js, config.full.js).
- If you have `content: []` or `content: { files: [] }` then the auto
  content feature won't be active. However if those arrays are empty a
  warning will still be shown. Adding files/globs or dropping the
  `content` section completely will enable auto content.

* only test the auto content integration test in the oxide engine

* set `content.files` to `auto` instead of using `auto: boolean`

This way we don't run into the issue where the `config.content.files` is
set and the `config.content.auto` is set to true.

* drop log

* ensure we validate the config in the CLI

* show experimental warning for automatic content detection

* use cached version of the getCandidateFiles instead of bypassing it

* use `is_empty()` shorthand

Thanks, Clippy!

* add test to ensure nested ignored folders are not scanned

* add `tempfile` for tests

* add auto content tests in Rust

* refactor auto content detection

This will also make sure that if we have (deeply) nested ignored
folders, then we won't use deeply nested globs (**/*.{js,html}) for the
parent(s) of the nested ignored folders but instead use a shallow glob
for each directory (*/*.{js,html}).

Then each sibling directory of the parent can use deeply nested globs
again except for the direct parent.

* use consistent comments

* ensure ignored static listed files are not present

* improve performance by ~30x

On a big test project this goes from ~6s to ~200ms

* improve performance by ~5x

We started with a ~6s duration
Then in the previous commit, we improved it by ~30x and it went down to
~200ms
Now with this change, it takes about ~40ms. That's another ~5x
improvement.

Or in total a ~150x improvement.

* ensure nested folders in `public/` are also explicitly listed

* add shortcut for normalizing files

This is only called once so won't do anything to the main performance of
Tailwind CSS. But always nice to make small performance improvements!

* run Rust tests in CI

* fix lint warnings

* update changelog

* Update CHANGELOG.md

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2023-05-12 18:49:35 +02:00
Robin Malfait
80415ece5e
Support @import "tailwindcss" using top-level index.css file (#11205)
* make `@import "tailwindcss"` work out of the box

* update changelog
2023-05-12 18:49:35 +02:00
depfu[bot]
ae5d746226
Update sass to version 1.62.1 2023-05-12 18:49:35 +02:00
depfu[bot]
8af8e0d1e6
Update cssnano to version 6.0.1 2023-05-12 18:49:35 +02:00
depfu[bot]
3e015818d6
Update postcss-selector-parser to version 6.0.12 2023-05-12 18:49:34 +02:00
depfu[bot]
9b6047d639
Update vite to version 4.3.4 2023-05-12 18:49:33 +02:00
depfu[bot]
9799e88244
Update @swc/core to version 1.3.56 2023-05-12 18:48:57 +02:00
Robin Malfait
a4314c052b
use older version of centos/nodejs-12 2023-05-12 18:47:41 +02:00
Robin Malfait
3309d3f635
[oxide] Expose experimental Rust parser setup (#11116)
* make `sequential` and `parallel` version of a new (tmp) `parse_candidate_strings`

* use bitmasks for the strategy

Only sending a number over the wire instead of a serialized objects.

* use cleaner match syntax
2023-05-12 18:47:41 +02:00
depfu[bot]
225a3fa3c6
Update resolve to version 1.22.3 2023-05-12 18:47:38 +02:00
Jordan Pittman
71205d8fcb
Revert "Update CODE_OF_CONDUCT.md (#10999)"
This reverts commit ba5454543e.
2023-05-12 18:45:43 +02:00
vjdevra
f8506be208
Update CODE_OF_CONDUCT.md (#10999) 2023-05-12 18:45:43 +02:00
Robin Malfait
7dbb59c9db
Revert prepare of v3.3 2023-05-12 18:45:43 +02:00
Robin Malfait
251cc37d7d
Ensure repeating-conic-gradient is detected as an image (#11180)
* ensure `repeating-conic-gradient` is detected as an image

* update changelog
2023-05-08 09:32:58 +02:00
Jordan Pittman
960cb40c6b
Fix parsing of theme() inside calc() when there are no spaces around operators (#11157)
* Refactor

* Don’t resolve functions for anything not using theme or screen

* Normalize math operators inside calc when handling functions

* Inline postcss-value-parser

* Treat all functions the same as calc

* Remove workaround for calc + operators without spaces

* Remove `postcss-value-parser` dependency

* Update lockfile

* Update sourcemaps

* Update changelog

* Update `value-parser` formatting

* Stop prettier from complaining
2023-05-04 10:20:01 -04:00
Adam Wathan
cdca9cbcfe
Update preflight.css 2023-04-28 22:39:08 -04:00
Jordan Pittman
9a57badc9f Update changelog 2023-04-28 12:54:09 -04:00
Eran Hirsch
7b3fe8de0f
Reset dialog element styles (#11069)
* disable useragent styling for dialog

* nits

* Update src/css/preflight.css

* Simplify dialog reset

We don’t want to reset everything here. Just the padding should be enough.

* Update test

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2023-04-28 12:51:52 -04:00
Anthony Ricaud
1f74b14513
Add aria-busy utility (#10966)
* Add aria-busy variant

This is useful to show a spinner or dim a region that is updating.

Very useful in conjunction with [Turbo](https://turbo.hotwired.dev/reference/attributes#automatically-added-attributes)

* update changelog

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2023-04-28 12:26:13 +02:00
Dany Castillo
1f1a6faf2c
Make font settings propagate into buttons, inputs, etc. (#10940)
* Make font settings propagate into buttons, inputs, etc.

* update source-maps test due to preflight change

* update changelog

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2023-04-28 10:06:04 +02:00
Jordan Pittman
ef056a57d3
Fix issues with some pseudo-elements (#11111)
* Fix issues with some pseudo-elements

We’ve included pseudo elements for backdrop, marker, placeholder, and selection and they were all “jumpable” before we made changes in v3.3.2. Ideally they wouldn’t be because if they ever eventually have any interactivity that could become a problem.

* Update changelog
2023-04-27 12:21:28 -04:00
628 changed files with 73823 additions and 56724 deletions

View file

@ -7,3 +7,11 @@ end_of_line = lf
charset = utf-8 charset = utf-8
trim_trailing_whitespace = true trim_trailing_whitespace = true
insert_final_newline = true insert_final_newline = true
[*.rs]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

View file

@ -7,3 +7,4 @@
/src/corePluginList.js /src/corePluginList.js
/oxide/crates/node/index.js /oxide/crates/node/index.js
/oxide/crates/node/index.d.ts /oxide/crates/node/index.d.ts
.github/actions/minus-one/index.mjs

7
.github/actions/minus-one/index.mjs vendored Normal file

File diff suppressed because one or more lines are too long

659
.github/actions/minus-one/licenses.txt vendored Normal file
View file

@ -0,0 +1,659 @@
@actions/core
MIT
The MIT License (MIT)
Copyright 2019 GitHub
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@actions/github
MIT
The MIT License (MIT)
Copyright 2019 GitHub
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@actions/http-client
MIT
Actions Http Client for Node.js
Copyright (c) GitHub, Inc.
All rights reserved.
MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@octokit/auth-token
MIT
The MIT License
Copyright (c) 2019 Octokit contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
@octokit/core
MIT
The MIT License
Copyright (c) 2019 Octokit contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
@octokit/endpoint
MIT
The MIT License
Copyright (c) 2018 Octokit contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
@octokit/graphql
MIT
The MIT License
Copyright (c) 2018 Octokit contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
@octokit/plugin-paginate-rest
MIT
MIT License Copyright (c) 2019 Octokit contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@octokit/plugin-rest-endpoint-methods
MIT
MIT License Copyright (c) 2019 Octokit contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@octokit/request
MIT
The MIT License
Copyright (c) 2018 Octokit contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
@octokit/request-error
MIT
The MIT License
Copyright (c) 2019 Octokit contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
@vercel/ncc
MIT
Copyright 2018 ZEIT, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
before-after-hook
Apache-2.0
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2018 Gregor Martynus and other contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
deprecation
ISC
The ISC License
Copyright (c) Gregor Martynus and contributors
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
is-plain-object
MIT
The MIT License (MIT)
Copyright (c) 2014-2017, Jon Schlinkert.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
nextjs-project
The MIT License (MIT)
Copyright (c) 2023 Vercel, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
node-fetch
MIT
The MIT License (MIT)
Copyright (c) 2016 David Frank
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
once
ISC
The ISC License
Copyright (c) Isaac Z. Schlueter and Contributors
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
tr46
MIT
tunnel
MIT
The MIT License (MIT)
Copyright (c) 2012 Koichi Kobayashi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
universal-user-agent
ISC
# [ISC License](https://spdx.org/licenses/ISC)
Copyright (c) 2018, Gregor Martynus (https://github.com/gr2m)
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
uuid
MIT
The MIT License (MIT)
Copyright (c) 2010-2020 Robert Kieffer and other contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
webidl-conversions
BSD-2-Clause
# The BSD 2-Clause License
Copyright (c) 2014, Domenic Denicola
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
whatwg-url
MIT
The MIT License (MIT)
Copyright (c) 2015–2016 Sebastian Mayr
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
wrappy
ISC
The ISC License
Copyright (c) Isaac Z. Schlueter and Contributors
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

572
.github/actions/minus-one/package-lock.json generated vendored Normal file
View file

@ -0,0 +1,572 @@
{
"name": "minus-one",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"dependencies": {
"@actions/core": "1.10.0",
"@actions/github": "5.1.1",
"@octokit/graphql": "7.0.1"
},
"devDependencies": {
"@vercel/ncc": "0.34.0"
}
},
"node_modules/@actions/core": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.10.0.tgz",
"integrity": "sha512-2aZDDa3zrrZbP5ZYg159sNoLRb61nQ7awl5pSvIq5Qpj81vwDzdMRKzkWJGJuwVvWpvZKx7vspJALyvaaIQyug==",
"dependencies": {
"@actions/http-client": "^2.0.1",
"uuid": "^8.3.2"
}
},
"node_modules/@actions/github": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/@actions/github/-/github-5.1.1.tgz",
"integrity": "sha512-Nk59rMDoJaV+mHCOJPXuvB1zIbomlKS0dmSIqPGxd0enAXBnOfn4VWF+CGtRCwXZG9Epa54tZA7VIRlJDS8A6g==",
"dependencies": {
"@actions/http-client": "^2.0.1",
"@octokit/core": "^3.6.0",
"@octokit/plugin-paginate-rest": "^2.17.0",
"@octokit/plugin-rest-endpoint-methods": "^5.13.0"
}
},
"node_modules/@actions/http-client": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz",
"integrity": "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==",
"dependencies": {
"tunnel": "^0.0.6"
}
},
"node_modules/@octokit/auth-token": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz",
"integrity": "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==",
"dependencies": {
"@octokit/types": "^6.0.3"
}
},
"node_modules/@octokit/core": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.6.0.tgz",
"integrity": "sha512-7RKRKuA4xTjMhY+eG3jthb3hlZCsOwg3rztWh75Xc+ShDWOfDDATWbeZpAHBNRpm4Tv9WgBMOy1zEJYXG6NJ7Q==",
"dependencies": {
"@octokit/auth-token": "^2.4.4",
"@octokit/graphql": "^4.5.8",
"@octokit/request": "^5.6.3",
"@octokit/request-error": "^2.0.5",
"@octokit/types": "^6.0.3",
"before-after-hook": "^2.2.0",
"universal-user-agent": "^6.0.0"
}
},
"node_modules/@octokit/core/node_modules/@octokit/graphql": {
"version": "4.8.0",
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz",
"integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==",
"dependencies": {
"@octokit/request": "^5.6.0",
"@octokit/types": "^6.0.3",
"universal-user-agent": "^6.0.0"
}
},
"node_modules/@octokit/endpoint": {
"version": "6.0.12",
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz",
"integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==",
"dependencies": {
"@octokit/types": "^6.0.3",
"is-plain-object": "^5.0.0",
"universal-user-agent": "^6.0.0"
}
},
"node_modules/@octokit/graphql": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.0.1.tgz",
"integrity": "sha512-T5S3oZ1JOE58gom6MIcrgwZXzTaxRnxBso58xhozxHpOqSTgDS6YNeEUvZ/kRvXgPrRz/KHnZhtb7jUMRi9E6w==",
"dependencies": {
"@octokit/request": "^8.0.1",
"@octokit/types": "^11.0.0",
"universal-user-agent": "^6.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/graphql/node_modules/@octokit/endpoint": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.0.tgz",
"integrity": "sha512-szrQhiqJ88gghWY2Htt8MqUDO6++E/EIXqJ2ZEp5ma3uGS46o7LZAzSLt49myB7rT+Hfw5Y6gO3LmOxGzHijAQ==",
"dependencies": {
"@octokit/types": "^11.0.0",
"is-plain-object": "^5.0.0",
"universal-user-agent": "^6.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/graphql/node_modules/@octokit/openapi-types": {
"version": "18.0.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-18.0.0.tgz",
"integrity": "sha512-V8GImKs3TeQRxRtXFpG2wl19V7444NIOTDF24AWuIbmNaNYOQMWRbjcGDXV5B+0n887fgDcuMNOmlul+k+oJtw=="
},
"node_modules/@octokit/graphql/node_modules/@octokit/request": {
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.1.1.tgz",
"integrity": "sha512-8N+tdUz4aCqQmXl8FpHYfKG9GelDFd7XGVzyN8rc6WxVlYcfpHECnuRkgquzz+WzvHTK62co5di8gSXnzASZPQ==",
"dependencies": {
"@octokit/endpoint": "^9.0.0",
"@octokit/request-error": "^5.0.0",
"@octokit/types": "^11.1.0",
"is-plain-object": "^5.0.0",
"universal-user-agent": "^6.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/graphql/node_modules/@octokit/request-error": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.0.0.tgz",
"integrity": "sha512-1ue0DH0Lif5iEqT52+Rf/hf0RmGO9NWFjrzmrkArpG9trFfDM/efx00BJHdLGuro4BR/gECxCU2Twf5OKrRFsQ==",
"dependencies": {
"@octokit/types": "^11.0.0",
"deprecation": "^2.0.0",
"once": "^1.4.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/graphql/node_modules/@octokit/types": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-11.1.0.tgz",
"integrity": "sha512-Fz0+7GyLm/bHt8fwEqgvRBWwIV1S6wRRyq+V6exRKLVWaKGsuy6H9QFYeBVDV7rK6fO3XwHgQOPxv+cLj2zpXQ==",
"dependencies": {
"@octokit/openapi-types": "^18.0.0"
}
},
"node_modules/@octokit/openapi-types": {
"version": "12.11.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-12.11.0.tgz",
"integrity": "sha512-VsXyi8peyRq9PqIz/tpqiL2w3w80OgVMwBHltTml3LmVvXiphgeqmY9mvBw9Wu7e0QWk/fqD37ux8yP5uVekyQ=="
},
"node_modules/@octokit/plugin-paginate-rest": {
"version": "2.21.3",
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.21.3.tgz",
"integrity": "sha512-aCZTEf0y2h3OLbrgKkrfFdjRL6eSOo8komneVQJnYecAxIej7Bafor2xhuDJOIFau4pk0i/P28/XgtbyPF0ZHw==",
"dependencies": {
"@octokit/types": "^6.40.0"
},
"peerDependencies": {
"@octokit/core": ">=2"
}
},
"node_modules/@octokit/plugin-rest-endpoint-methods": {
"version": "5.16.2",
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.16.2.tgz",
"integrity": "sha512-8QFz29Fg5jDuTPXVtey05BLm7OB+M8fnvE64RNegzX7U+5NUXcOcnpTIK0YfSHBg8gYd0oxIq3IZTe9SfPZiRw==",
"dependencies": {
"@octokit/types": "^6.39.0",
"deprecation": "^2.3.1"
},
"peerDependencies": {
"@octokit/core": ">=3"
}
},
"node_modules/@octokit/request": {
"version": "5.6.3",
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.3.tgz",
"integrity": "sha512-bFJl0I1KVc9jYTe9tdGGpAMPy32dLBXXo1dS/YwSCTL/2nd9XeHsY616RE3HPXDVk+a+dBuzyz5YdlXwcDTr2A==",
"dependencies": {
"@octokit/endpoint": "^6.0.1",
"@octokit/request-error": "^2.1.0",
"@octokit/types": "^6.16.1",
"is-plain-object": "^5.0.0",
"node-fetch": "^2.6.7",
"universal-user-agent": "^6.0.0"
}
},
"node_modules/@octokit/request-error": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz",
"integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==",
"dependencies": {
"@octokit/types": "^6.0.3",
"deprecation": "^2.0.0",
"once": "^1.4.0"
}
},
"node_modules/@octokit/types": {
"version": "6.41.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.41.0.tgz",
"integrity": "sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg==",
"dependencies": {
"@octokit/openapi-types": "^12.11.0"
}
},
"node_modules/@vercel/ncc": {
"version": "0.34.0",
"resolved": "https://registry.npmjs.org/@vercel/ncc/-/ncc-0.34.0.tgz",
"integrity": "sha512-G9h5ZLBJ/V57Ou9vz5hI8pda/YQX5HQszCs3AmIus3XzsmRn/0Ptic5otD3xVST8QLKk7AMk7AqpsyQGN7MZ9A==",
"dev": true,
"bin": {
"ncc": "dist/ncc/cli.js"
}
},
"node_modules/before-after-hook": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz",
"integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ=="
},
"node_modules/deprecation": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz",
"integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ=="
},
"node_modules/is-plain-object": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/node-fetch": {
"version": "2.6.12",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz",
"integrity": "sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"dependencies": {
"wrappy": "1"
}
},
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
},
"node_modules/tunnel": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==",
"engines": {
"node": ">=0.6.11 <=0.7.0 || >=0.7.3"
}
},
"node_modules/universal-user-agent": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz",
"integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w=="
},
"node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
}
},
"dependencies": {
"@actions/core": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.10.0.tgz",
"integrity": "sha512-2aZDDa3zrrZbP5ZYg159sNoLRb61nQ7awl5pSvIq5Qpj81vwDzdMRKzkWJGJuwVvWpvZKx7vspJALyvaaIQyug==",
"requires": {
"@actions/http-client": "^2.0.1",
"uuid": "^8.3.2"
}
},
"@actions/github": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/@actions/github/-/github-5.1.1.tgz",
"integrity": "sha512-Nk59rMDoJaV+mHCOJPXuvB1zIbomlKS0dmSIqPGxd0enAXBnOfn4VWF+CGtRCwXZG9Epa54tZA7VIRlJDS8A6g==",
"requires": {
"@actions/http-client": "^2.0.1",
"@octokit/core": "^3.6.0",
"@octokit/plugin-paginate-rest": "^2.17.0",
"@octokit/plugin-rest-endpoint-methods": "^5.13.0"
}
},
"@actions/http-client": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz",
"integrity": "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==",
"requires": {
"tunnel": "^0.0.6"
}
},
"@octokit/auth-token": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz",
"integrity": "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==",
"requires": {
"@octokit/types": "^6.0.3"
}
},
"@octokit/core": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.6.0.tgz",
"integrity": "sha512-7RKRKuA4xTjMhY+eG3jthb3hlZCsOwg3rztWh75Xc+ShDWOfDDATWbeZpAHBNRpm4Tv9WgBMOy1zEJYXG6NJ7Q==",
"requires": {
"@octokit/auth-token": "^2.4.4",
"@octokit/graphql": "^4.5.8",
"@octokit/request": "^5.6.3",
"@octokit/request-error": "^2.0.5",
"@octokit/types": "^6.0.3",
"before-after-hook": "^2.2.0",
"universal-user-agent": "^6.0.0"
},
"dependencies": {
"@octokit/graphql": {
"version": "4.8.0",
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz",
"integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==",
"requires": {
"@octokit/request": "^5.6.0",
"@octokit/types": "^6.0.3",
"universal-user-agent": "^6.0.0"
}
}
}
},
"@octokit/endpoint": {
"version": "6.0.12",
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz",
"integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==",
"requires": {
"@octokit/types": "^6.0.3",
"is-plain-object": "^5.0.0",
"universal-user-agent": "^6.0.0"
}
},
"@octokit/graphql": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.0.1.tgz",
"integrity": "sha512-T5S3oZ1JOE58gom6MIcrgwZXzTaxRnxBso58xhozxHpOqSTgDS6YNeEUvZ/kRvXgPrRz/KHnZhtb7jUMRi9E6w==",
"requires": {
"@octokit/request": "^8.0.1",
"@octokit/types": "^11.0.0",
"universal-user-agent": "^6.0.0"
},
"dependencies": {
"@octokit/endpoint": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.0.tgz",
"integrity": "sha512-szrQhiqJ88gghWY2Htt8MqUDO6++E/EIXqJ2ZEp5ma3uGS46o7LZAzSLt49myB7rT+Hfw5Y6gO3LmOxGzHijAQ==",
"requires": {
"@octokit/types": "^11.0.0",
"is-plain-object": "^5.0.0",
"universal-user-agent": "^6.0.0"
}
},
"@octokit/openapi-types": {
"version": "18.0.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-18.0.0.tgz",
"integrity": "sha512-V8GImKs3TeQRxRtXFpG2wl19V7444NIOTDF24AWuIbmNaNYOQMWRbjcGDXV5B+0n887fgDcuMNOmlul+k+oJtw=="
},
"@octokit/request": {
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.1.1.tgz",
"integrity": "sha512-8N+tdUz4aCqQmXl8FpHYfKG9GelDFd7XGVzyN8rc6WxVlYcfpHECnuRkgquzz+WzvHTK62co5di8gSXnzASZPQ==",
"requires": {
"@octokit/endpoint": "^9.0.0",
"@octokit/request-error": "^5.0.0",
"@octokit/types": "^11.1.0",
"is-plain-object": "^5.0.0",
"universal-user-agent": "^6.0.0"
}
},
"@octokit/request-error": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.0.0.tgz",
"integrity": "sha512-1ue0DH0Lif5iEqT52+Rf/hf0RmGO9NWFjrzmrkArpG9trFfDM/efx00BJHdLGuro4BR/gECxCU2Twf5OKrRFsQ==",
"requires": {
"@octokit/types": "^11.0.0",
"deprecation": "^2.0.0",
"once": "^1.4.0"
}
},
"@octokit/types": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-11.1.0.tgz",
"integrity": "sha512-Fz0+7GyLm/bHt8fwEqgvRBWwIV1S6wRRyq+V6exRKLVWaKGsuy6H9QFYeBVDV7rK6fO3XwHgQOPxv+cLj2zpXQ==",
"requires": {
"@octokit/openapi-types": "^18.0.0"
}
}
}
},
"@octokit/openapi-types": {
"version": "12.11.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-12.11.0.tgz",
"integrity": "sha512-VsXyi8peyRq9PqIz/tpqiL2w3w80OgVMwBHltTml3LmVvXiphgeqmY9mvBw9Wu7e0QWk/fqD37ux8yP5uVekyQ=="
},
"@octokit/plugin-paginate-rest": {
"version": "2.21.3",
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.21.3.tgz",
"integrity": "sha512-aCZTEf0y2h3OLbrgKkrfFdjRL6eSOo8komneVQJnYecAxIej7Bafor2xhuDJOIFau4pk0i/P28/XgtbyPF0ZHw==",
"requires": {
"@octokit/types": "^6.40.0"
}
},
"@octokit/plugin-rest-endpoint-methods": {
"version": "5.16.2",
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.16.2.tgz",
"integrity": "sha512-8QFz29Fg5jDuTPXVtey05BLm7OB+M8fnvE64RNegzX7U+5NUXcOcnpTIK0YfSHBg8gYd0oxIq3IZTe9SfPZiRw==",
"requires": {
"@octokit/types": "^6.39.0",
"deprecation": "^2.3.1"
}
},
"@octokit/request": {
"version": "5.6.3",
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.3.tgz",
"integrity": "sha512-bFJl0I1KVc9jYTe9tdGGpAMPy32dLBXXo1dS/YwSCTL/2nd9XeHsY616RE3HPXDVk+a+dBuzyz5YdlXwcDTr2A==",
"requires": {
"@octokit/endpoint": "^6.0.1",
"@octokit/request-error": "^2.1.0",
"@octokit/types": "^6.16.1",
"is-plain-object": "^5.0.0",
"node-fetch": "^2.6.7",
"universal-user-agent": "^6.0.0"
}
},
"@octokit/request-error": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz",
"integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==",
"requires": {
"@octokit/types": "^6.0.3",
"deprecation": "^2.0.0",
"once": "^1.4.0"
}
},
"@octokit/types": {
"version": "6.41.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.41.0.tgz",
"integrity": "sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg==",
"requires": {
"@octokit/openapi-types": "^12.11.0"
}
},
"@vercel/ncc": {
"version": "0.34.0",
"resolved": "https://registry.npmjs.org/@vercel/ncc/-/ncc-0.34.0.tgz",
"integrity": "sha512-G9h5ZLBJ/V57Ou9vz5hI8pda/YQX5HQszCs3AmIus3XzsmRn/0Ptic5otD3xVST8QLKk7AMk7AqpsyQGN7MZ9A==",
"dev": true
},
"before-after-hook": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz",
"integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ=="
},
"deprecation": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz",
"integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ=="
},
"is-plain-object": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q=="
},
"node-fetch": {
"version": "2.6.12",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz",
"integrity": "sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==",
"requires": {
"whatwg-url": "^5.0.0"
}
},
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"requires": {
"wrappy": "1"
}
},
"tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
},
"tunnel": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="
},
"universal-user-agent": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz",
"integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w=="
},
"uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
},
"webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
},
"whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"requires": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
}
}
}

21
.github/actions/minus-one/package.json vendored Normal file
View file

@ -0,0 +1,21 @@
{
"private": true,
"description": "Manage +1 comments on GitHub issues",
"author": "Balázs Orbán <info@balazsorban.com>",
"exports": "./index.mjs",
"files": [
"src"
],
"scripts": {
"build": "ncc -m -o . build src/index.mjs --license licenses.txt"
},
"devDependencies": {
"@vercel/ncc": "0.34.0"
},
"dependencies": {
"@actions/core": "1.10.0",
"@actions/github": "5.1.1",
"@octokit/graphql": "7.0.1"
},
"packageManager": "npm@9.2.0"
}

63
.github/actions/minus-one/src/index.mjs vendored Normal file
View file

@ -0,0 +1,63 @@
// @ts-check
import { context } from '@actions/github'
import { info, setFailed } from '@actions/core'
import { graphql } from '@octokit/graphql'
if (!process.env.GITHUB_TOKEN) throw new Error('GITHUB_TOKEN not set')
/**
MIT License
Copyright (c) Sindre Sorhus sindresorhus@gmail.com (sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
function isUnhelpfulComment(text) {
// Borrowed from Refined GitHub:
// https://github.com/refined-github/refined-github/blob/c864a20b57bb433aaf3952f88d83c9fc481ae6ff/source/helpers/is-low-quality-comment.ts#L2-L3
return (
text.replace(
/[\s,.!?👍👎👌🙏]+|[\u{1F3FB}-\u{1F3FF}]|[+-]\d+|⬆️|ditt?o|me|too|t?here|on|same|this|issues?|please|pl[sz]|any|updates?|bump|question|solution|following/giu,
''
) === ''
)
}
async function run() {
try {
const { comment } = context.payload
if (!comment) return
const { node_id: subjectId, body } = comment
if (isUnhelpfulComment(body)) {
await graphql(
`
mutation minimize($subjectId: ID!) {
minimizeComment(
input: { subjectId: $subjectId, classifier: OFF_TOPIC }
) {
minimizedComment {
isMinimized
}
}
}
`,
{
subjectId,
headers: { authorization: `token ${process.env.GITHUB_TOKEN}` },
}
)
info(`Comment (${body.slice(0, 15)}) was minimized.`)
}
} catch (error) {
setFailed(error)
}
}
run()

View file

@ -1,59 +0,0 @@
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
name: CI — Stable
on:
push:
branches: [master]
pull_request:
branches: [master, 3.3]
permissions:
contents: read
env:
CI: true
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
CACHE_PREFIX: stable
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [14, 18]
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: Use the `stable` engine
run: |
node ./scripts/swap-engines.js
- name: Cache node_modules
uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.os }}-${{ matrix.node-version }}-${{ env.CACHE_PREFIX }}-node_modules-${{ hashFiles('**/package-lock.json') }}
- name: Install dependencies
run: npm install
- name: Build Tailwind CSS
run: npm run build
- name: Test
run: |
npm run test || \
npm run test || \
npm run test || exit 1
- name: Lint
run: npm run style

View file

@ -1,10 +1,10 @@
name: CI — Oxide name: CI
on: on:
push: push:
branches: [master, 3.3] branches: [master]
pull_request: pull_request:
branches: [master, 3.3] branches: [master]
permissions: permissions:
contents: read contents: read
@ -13,11 +13,11 @@ env:
CI: true CI: true
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }} TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
CACHE_PREFIX: oxide
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 15
strategy: strategy:
matrix: matrix:
@ -34,7 +34,7 @@ jobs:
uses: actions/cache@v3 uses: actions/cache@v3
with: with:
path: node_modules path: node_modules
key: ${{ runner.os }}-${{ matrix.node-version }}-${{ env.CACHE_PREFIX }}-node_modules-${{ hashFiles('**/package-lock.json') }} key: ${{ runner.os }}-${{ matrix.node-version }}-node_modules-${{ hashFiles('**/package-lock.json') }}
# Cargo already skips downloading dependencies if they already exist # Cargo already skips downloading dependencies if they already exist
- name: Cache cargo - name: Cache cargo
@ -62,14 +62,29 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: npm install run: npm install
- name: Check versions
run: |
echo "Node:" `node --version`
echo "NPM:" `npm --version`
echo "Rust:" `rustc --version`
echo "Cargo:" `cargo --version`
- name: Build Tailwind CSS - name: Build Tailwind CSS
run: npx turbo run build --filter=// run: npx turbo run build --filter=//
- name: Test - name: Test (Node.js)
run: | run: |
npx turbo run test --filter=// || \ npx turbo run test --filter=// || \
npx turbo run test --filter=// || \ npx turbo run test --filter=// || \
npx turbo run test --filter=// || exit 1 npx turbo run test --filter=// || exit 1
- name: Test (Rust)
run: |
cd ./oxide
cargo test || \
cargo test || \
cargo test || exit 1
cd -
- name: Lint - name: Lint
run: npx turbo run style --filter=// run: npx turbo run style --filter=//

View file

@ -1,83 +0,0 @@
name: Integration Tests — Stable
on:
push:
branches: [master, 3.3]
pull_request:
branches: [master, 3.3]
permissions:
contents: read
env:
CI: true
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
CACHE_PREFIX: stable
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
integration:
- content-resolution
- parcel
- postcss-cli
- rollup
- rollup-sass
- tailwindcss-cli
- vite
- webpack-4
- webpack-5
node-version: [18]
fail-fast: false
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: Use the `stable` engine
run: |
node ./scripts/swap-engines.js
- name: Cache node_modules (tailwindcss)
uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.os }}-${{ matrix.node-version }}-${{ env.CACHE_PREFIX }}-node_modules-${{ hashFiles('package-lock.json') }}
- name: Cache node_modules (integrations)
uses: actions/cache@v3
with:
path: ./integrations/node_modules
key: ${{ runner.os }}-${{ matrix.node-version }}-${{ env.CACHE_PREFIX }}-node_modules-integrations-${{ hashFiles('./integrations/package-lock.json') }}
- name: Cache node_modules (integration)
uses: actions/cache@v3
with:
path: ./integrations/${{ matrix.integration }}/node_modules
key: ${{ runner.os }}-${{ matrix.node-version }}-${{ env.CACHE_PREFIX }}-node_modules-integrations-${{ matrix.integration }}-${{ hashFiles('./integrations/${{ matrix.integration }}/package-lock.json') }}
- name: Install dependencies
run: npm install
- name: Install dependencies (shared integrations)
run: npm install --prefix ./integrations
- name: Install dependencies (integration)
run: npm install --prefix ./integrations/${{ matrix.integration }}
- name: Build Tailwind CSS
run: npm run build
- name: Test ${{ matrix.integration }}
run: |
npm run test --prefix ./integrations/${{ matrix.integration }} || \
npm run test --prefix ./integrations/${{ matrix.integration }} || \
npm run test --prefix ./integrations/${{ matrix.integration }} || exit 1

View file

@ -1,10 +1,10 @@
name: Integration Tests — Oxide name: Integration Tests
on: on:
push: push:
branches: [master, 3.3] branches: [master]
pull_request: pull_request:
branches: [master, 3.3] branches: [master]
permissions: permissions:
contents: read contents: read
@ -13,11 +13,11 @@ env:
CI: true CI: true
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }} TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
CACHE_PREFIX: oxide
jobs: jobs:
test: test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 15
strategy: strategy:
matrix: matrix:
@ -46,7 +46,7 @@ jobs:
uses: actions/cache@v3 uses: actions/cache@v3
with: with:
path: node_modules path: node_modules
key: ${{ runner.os }}-${{ matrix.node-version }}-${{ env.CACHE_PREFIX }}-node_modules-${{ hashFiles('**/package-lock.json') }} key: ${{ runner.os }}-${{ matrix.node-version }}-node_modules-${{ hashFiles('**/package-lock.json') }}
# Cargo already skips downloading dependencies if they already exist # Cargo already skips downloading dependencies if they already exist
- name: Cache cargo - name: Cache cargo
@ -74,6 +74,13 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: npm install run: npm install
- name: Check versions
run: |
echo "Node:" `node --version`
echo "NPM:" `npm --version`
echo "Rust:" `rustc --version`
echo "Cargo:" `cargo --version`
- name: Build Tailwind CSS - name: Build Tailwind CSS
run: npx turbo run build --filter=// run: npx turbo run build --filter=//

18
.github/workflows/issue_on_comment.yml vendored Normal file
View file

@ -0,0 +1,18 @@
name: On issue comment
on:
issue_comment:
types: [created]
jobs:
minus-one:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- name: 'Manage +1 comments'
run: node ./.github/actions/minus-one/index.mjs
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View file

@ -18,6 +18,7 @@ jobs:
contents: write # for softprops/action-gh-release to create GitHub release contents: write # for softprops/action-gh-release to create GitHub release
runs-on: macos-11 runs-on: macos-11
timeout-minutes: 15
strategy: strategy:
matrix: matrix:
@ -53,6 +54,13 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: npm install run: npm install
- name: Check versions
run: |
echo "Node:" `node --version`
echo "NPM:" `npm --version`
echo "Rust:" `rustc --version`
echo "Cargo:" `cargo --version`
- name: Build Tailwind CSS - name: Build Tailwind CSS
run: npm run build run: npm run build

View file

@ -1,78 +0,0 @@
name: Release Insiders — Stable
on:
push:
branches: [master]
permissions:
contents: read
env:
CI: true
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
CACHE_PREFIX: stable
NODE_VERSION: 16
RELEASE_CHANNEL: insiders
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
registry-url: 'https://registry.npmjs.org'
- name: Use the `stable` engine
run: |
node ./scripts/swap-engines.js
- name: Cache node_modules
uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.os }}-${{ env.NODE_VERSION }}-${{ env.CACHE_PREFIX }}-node_modules-${{ hashFiles('**/package-lock.json') }}
- name: Install dependencies
run: npm install
- name: Build Tailwind CSS
run: npm run build
- name: Test
run: |
npm run test || \
npm run test || \
npm run test || exit 1
- name: Resolve version
id: vars
run: |
echo "SHA_SHORT=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
- name: 'Version based on commit: 0.0.0-${{ env.RELEASE_CHANNEL }}.${{ env.SHA_SHORT }}'
run: npm version 0.0.0-${{ env.RELEASE_CHANNEL }}.${{ env.SHA_SHORT }} --force --no-git-tag-version
- name: Publish
run: npm publish --tag ${{ env.RELEASE_CHANNEL }}
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Trigger Tailwind Play update
uses: actions/github-script@v6
with:
github-token: ${{ secrets.TAILWIND_PLAY_TOKEN }}
script: |
await github.rest.actions.createWorkflowDispatch({
owner: 'tailwindlabs',
repo: 'play.tailwindcss.com',
ref: 'master',
workflow_id: 'upgrade-tailwindcss.yml',
inputs: {
insidersVersion: '0.0.0-${{ env.RELEASE_CHANNEL }}.${{ env.SHA_SHORT }}'
}
})

View file

@ -1,4 +1,4 @@
name: Release Insiders — Oxide name: Release Insiders
on: on:
push: push:
@ -8,7 +8,7 @@ env:
APP_NAME: tailwindcss-oxide APP_NAME: tailwindcss-oxide
NODE_VERSION: 16 NODE_VERSION: 16
OXIDE_LOCATION: ./oxide/crates/node OXIDE_LOCATION: ./oxide/crates/node
RELEASE_CHANNEL: oxide-insiders RELEASE_CHANNEL: insiders
jobs: jobs:
build: build:
@ -26,6 +26,7 @@ jobs:
name: Build ${{ matrix.target }} (OXIDE) name: Build ${{ matrix.target }} (OXIDE)
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
timeout-minutes: 15
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@ -63,13 +64,6 @@ jobs:
with: with:
node-version: ${{ env.NODE_VERSION }} node-version: ${{ env.NODE_VERSION }}
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
- name: Setup rust target - name: Setup rust target
run: rustup target add ${{ matrix.target }} run: rustup target add ${{ matrix.target }}
@ -96,6 +90,7 @@ jobs:
build-apple-silicon: build-apple-silicon:
name: Build Apple Silicon (OXIDE) name: Build Apple Silicon (OXIDE)
runs-on: macos-latest runs-on: macos-latest
timeout-minutes: 15
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@ -133,13 +128,6 @@ jobs:
with: with:
node-version: ${{ env.NODE_VERSION }} node-version: ${{ env.NODE_VERSION }}
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
- name: Setup rust target - name: Setup rust target
run: rustup target add aarch64-apple-darwin run: rustup target add aarch64-apple-darwin
@ -170,24 +158,28 @@ jobs:
include: include:
- target: x86_64-unknown-linux-gnu - target: x86_64-unknown-linux-gnu
strip: strip strip: strip
image: docker.io/centos/nodejs-14-centos7 image: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian
download: false
- target: aarch64-unknown-linux-gnu - target: aarch64-unknown-linux-gnu
strip: aarch64-linux-gnu-strip strip: llvm-strip
image: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian image: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian-aarch64
setup: apt install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu -y download: false
- target: armv7-unknown-linux-gnueabihf - target: armv7-unknown-linux-gnueabihf
strip: arm-linux-gnueabihf-strip strip: llvm-strip
image: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian image: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian-zig
setup: apt install gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf -y download: false
- target: aarch64-unknown-linux-musl - target: aarch64-unknown-linux-musl
image: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine image: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine
strip: aarch64-linux-musl-strip strip: aarch64-linux-musl-strip
download: true
- target: x86_64-unknown-linux-musl - target: x86_64-unknown-linux-musl
image: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine image: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine
strip: strip strip: strip
download: true
name: Build ${{ matrix.target }} (OXIDE) name: Build ${{ matrix.target }} (OXIDE)
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 15
container: container:
image: ${{ matrix.image }} image: ${{ matrix.image }}
@ -228,16 +220,10 @@ jobs:
with: with:
node-version: ${{ env.NODE_VERSION }} node-version: ${{ env.NODE_VERSION }}
- name: Install Rust - name: Install Rust (Stable)
uses: actions-rs/toolchain@v1 if: ${{ matrix.download }}
with: run: |
toolchain: stable rustup default stable
profile: minimal
override: true
- name: Setup cross compile toolchain
if: ${{ matrix.setup }}
run: ${{ matrix.setup }}
- name: Setup rust target - name: Setup rust target
run: rustup target add ${{ matrix.target }} run: rustup target add ${{ matrix.target }}
@ -264,6 +250,7 @@ jobs:
release: release:
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 15
name: Build and release name: Build and release
needs: needs:
- build - build
@ -353,8 +340,9 @@ jobs:
env: env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
tailwind_oxide_release: tailwind-release:
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 15
name: Build and release Tailwind CSS name: Build and release Tailwind CSS
needs: needs:

View file

@ -1,332 +0,0 @@
name: Release — Oxide
on:
workflow_dispatch:
env:
APP_NAME: tailwindcss-oxide
NODE_VERSION: 14
OXIDE_LOCATION: ./oxide/crates/node
jobs:
oxide-build:
strategy:
fail-fast: false
matrix:
include:
# Windows
- os: windows-latest
target: x86_64-pc-windows-msvc
# Mac OS
- os: macos-latest
target: x86_64-apple-darwin
strip: strip -x # Must use -x on macOS. This produces larger results on linux.
name: Build ${{ matrix.target }} (OXIDE)
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
- name: Install Node.JS
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
- name: Setup rust target
run: rustup target add ${{ matrix.target }}
# - name: Use cached oxide node_modules
# id: cache-oxide
# uses: actions/cache@v3
# with:
# path: ${{ env.OXIDE_LOCATION }}/node_modules
# key: nodeModules-${{ hashFiles('${{ env.OXIDE_LOCATION }}/package-lock.json') }}-${{ matrix.os }}-${{ matrix.target }}-${{ env.NODE_VERSION }}
# restore-keys: |
# nodeModules-
- name: Oxide — npm install
# if: steps.cache-oxide.outputs.cache-hit != 'true'
run: cd ${{ env.OXIDE_LOCATION }} && npm install
- name: Build release
run: cd ${{ env.OXIDE_LOCATION }} && npm run build
env:
RUST_TARGET: ${{ matrix.target }}
- name: Strip debug symbols # https://github.com/rust-lang/rust/issues/46034
if: ${{ matrix.strip }}
run: ${{ matrix.strip }} ${{ env.OXIDE_LOCATION }}/*.node
- name: Upload artifacts
uses: actions/upload-artifact@v3
with:
name: bindings-${{ matrix.target }}
path: |
${{ env.OXIDE_LOCATION }}/*.node
${{ matrix.binary }}
oxide-build-apple-silicon:
name: Build Apple Silicon (OXIDE)
runs-on: macos-latest
steps:
- uses: actions/checkout@v3
- name: Cache node_modules
uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.os }}-${{ matrix.node-version }}-node_modules-${{ hashFiles('**/package-lock.json') }}
# Cargo already skips downloading dependencies if they already exist
- name: Cache cargo
uses: actions/cache@v3
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-${{ hashFiles('./oxide/**/Cargo.lock') }}
- name: Install Node.JS
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
- name: Setup rust target
run: rustup target add aarch64-apple-darwin
- name: Install dependencies
run: npm install
- name: Build release
run: cd ${{ env.OXIDE_LOCATION }} && npm run build
env:
RUST_TARGET: aarch64-apple-darwin
JEMALLOC_SYS_WITH_LG_PAGE: 14
- name: Strip debug symbols # https://github.com/rust-lang/rust/issues/46034
run: strip -x ${{ env.OXIDE_LOCATION }}/*.node
- name: Upload artifacts
uses: actions/upload-artifact@v3
with:
name: bindings-aarch64-apple-darwin
path: |
${{ env.OXIDE_LOCATION }}/*.node
${{ env.APP_NAME }}
oxide-build-linux:
strategy:
fail-fast: false
matrix:
include:
- target: x86_64-unknown-linux-gnu
strip: strip
image: docker.io/centos/nodejs-14-centos7
- target: aarch64-unknown-linux-gnu
strip: aarch64-linux-gnu-strip
image: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian
setup: apt install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu -y
- target: armv7-unknown-linux-gnueabihf
strip: arm-linux-gnueabihf-strip
image: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian
setup: apt install gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf -y
- target: aarch64-unknown-linux-musl
image: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine
strip: aarch64-linux-musl-strip
- target: x86_64-unknown-linux-musl
image: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine
strip: strip
name: Build ${{ matrix.target }} (OXIDE)
runs-on: ubuntu-latest
container:
image: ${{ matrix.image }}
steps:
- uses: actions/checkout@v3
- name: Install Node.JS
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
- name: Setup cross compile toolchain
if: ${{ matrix.setup }}
run: ${{ matrix.setup }}
- name: Setup rust target
run: rustup target add ${{ matrix.target }}
# - name: Use cached oxide node_modules
# id: cache-oxide
# uses: actions/cache@v3
# with:
# path: ${{ env.OXIDE_LOCATION }}/node_modules
# key: nodeModules-${{ hashFiles('${{ env.OXIDE_LOCATION }}/package-lock.json') }}-linux-${{ matrix.target }}-${{ env.NODE_VERSION }}
# restore-keys: |
# nodeModules-
- name: Oxide — npm install
# if: steps.cache-oxide.outputs.cache-hit != 'true'
run: cd ${{ env.OXIDE_LOCATION }} && npm install
- name: Build release
run: cd ${{ env.OXIDE_LOCATION }} && npm run build
env:
RUST_TARGET: ${{ matrix.target }}
- name: Strip debug symbols # https://github.com/rust-lang/rust/issues/46034
if: ${{ matrix.strip }}
run: ${{ matrix.strip }} ${{ env.OXIDE_LOCATION }}/*.node
- name: Upload artifacts
uses: actions/upload-artifact@v3
with:
name: bindings-${{ matrix.target }}
path: |
${{ env.OXIDE_LOCATION }}/*.node
${{ env.APP_NAME }}
oxide-release:
runs-on: ubuntu-latest
name: Build and release
needs:
- oxide-build
- oxide-build-linux
- oxide-build-apple-silicon
steps:
- uses: actions/checkout@v3
# - name: Use cached oxide node_modules
# id: cache-oxide
# uses: actions/cache@v3
# with:
# path: ${{ env.OXIDE_LOCATION }}/node_modules
# key: nodeModules-${{ hashFiles('${{ env.OXIDE_LOCATION }}/package-lock.json') }}-ubuntu-latest-${{ env.NODE_VERSION }}
# restore-keys: |
# nodeModules-
- name: Oxide — npm install
# if: steps.cache-oxide.outputs.cache-hit != 'true'
run: cd ${{ env.OXIDE_LOCATION }} && npm install
- name: Download artifacts
uses: actions/download-artifact@v3
with:
path: ${{ env.OXIDE_LOCATION }}
- run: echo //registry.npmjs.org/:_authToken=${NPM_TOKEN} > ~/.npmrc
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Move artifacts
run: |
cd ${{ env.OXIDE_LOCATION }}
cp bindings-aarch64-apple-darwin/oxide/crates/node/* ./npm/darwin-arm64/
cp bindings-aarch64-unknown-linux-gnu/oxide/crates/node/* ./npm/linux-arm64-gnu/
cp bindings-aarch64-unknown-linux-musl/oxide/crates/node/* ./npm/linux-arm64-musl/
cp bindings-armv7-unknown-linux-gnueabihf/oxide/crates/node/* ./npm/linux-arm-gnueabihf/
cp bindings-x86_64-apple-darwin/* ./npm/darwin-x64/
cp bindings-x86_64-pc-windows-msvc/* ./npm/win32-x64-msvc/
cp bindings-x86_64-unknown-linux-gnu/oxide/crates/node/* ./npm/linux-x64-gnu/
cp bindings-x86_64-unknown-linux-musl/oxide/crates/node/* ./npm/linux-x64-musl/
- name: Resolve version
id: vars
run: |
echo "NPM_VERSION=0.0.0-insiders.$(git rev-parse --short HEAD)" >> $GITHUB_ENV
- name: Generate entry point
run: npm run build --prefix ${{ env.OXIDE_LOCATION }}
- name: Publish to npm
run: |
for pkg in ${{ env.OXIDE_LOCATION }}/npm/*; do
echo "Publishing $pkg..."
cd $pkg
npm version ${{ env.NPM_VERSION }} --force --no-git-tag-version
npm publish --tag insiders --access public
cd -
done
echo "Publishing ${{ env.APP_NAME }}...";
cd ${{ env.OXIDE_LOCATION }}
npm version ${{ env.NPM_VERSION }} --force --no-git-tag-version
sed "s#\"0.0.0\"#\"${{ env.NPM_VERSION }}\"#g" package.json > package_updated.json
mv package_updated.json package.json
npm publish --tag insiders --access public
cd -
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
tailwind_oxide_release:
runs-on: ubuntu-latest
name: Build and release Tailwind CSS
needs:
- oxide-release
strategy:
matrix:
node-version: [14]
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
registry-url: 'https://registry.npmjs.org'
- name: Resolve version
id: vars
run: |
echo "SHA_SHORT=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
echo "OXIDE_NPM_VERSION=0.0.0-insiders.$(git rev-parse --short HEAD)" >> $GITHUB_ENV
- name: Setup `@tailwindcss/oxide` version
run: |
sed "s#\"@tailwindcss/oxide\": \".*\"#\"@tailwindcss/oxide\": \"${{ env.OXIDE_NPM_VERSION }}\"#" package.json > package_updated.json
mv package_updated.json package.json
- name: Install dependencies
run: npm install
- name: Build Tailwind CSS
run: npm run build
- name: Test
run: npm test
- name: 'Version based on commit: 0.0.0-oxide.${{ env.SHA_SHORT }}'
run: npm version 0.0.0-oxide.${{ env.SHA_SHORT }} --force --no-git-tag-version
- name: Publish
run: npm publish --tag oxide
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

View file

@ -1,68 +0,0 @@
name: Release — Stable
on:
release:
types: [published]
permissions:
contents: read
env:
CI: true
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [14]
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
registry-url: 'https://registry.npmjs.org'
cache: 'npm'
- name: Use the `stable` engine
run: |
node ./scripts/swap-engines.js
- name: Install dependencies
run: npm install
- name: Build Tailwind CSS
run: npm run build
- name: Test
run: npm run test
- name: Calculate environment variables
run: |
echo "RELEASE_CHANNEL=$(node ./scripts/release-channel.js)" >> $GITHUB_ENV
echo "TAILWINDCSS_VERSION=$(node -e 'console.log(require(`./package.json`).version);')" >> $GITHUB_ENV
- name: Publish
run: npm publish --tag ${{ env.RELEASE_CHANNEL }}
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Trigger Tailwind Play update
if: env.RELEASE_CHANNEL == 'latest'
uses: actions/github-script@v6
with:
github-token: ${{ secrets.TAILWIND_PLAY_TOKEN }}
script: |
await github.rest.actions.createWorkflowDispatch({
owner: 'tailwindlabs',
repo: 'play.tailwindcss.com',
ref: 'master',
workflow_id: 'upgrade-tailwindcss.yml',
inputs: {
version: '${{ env.TAILWINDCSS_VERSION }}'
}
})

429
.github/workflows/release.yml vendored Normal file
View file

@ -0,0 +1,429 @@
name: Release
on:
release:
types: [published]
permissions:
contents: read
env:
APP_NAME: tailwindcss-oxide
NODE_VERSION: 16
OXIDE_LOCATION: ./oxide/crates/node
jobs:
build:
strategy:
fail-fast: false
matrix:
include:
# Windows
- os: windows-latest
target: x86_64-pc-windows-msvc
# Mac OS
- os: macos-latest
target: x86_64-apple-darwin
strip: strip -x # Must use -x on macOS. This produces larger results on linux.
name: Build ${{ matrix.target }} (OXIDE)
runs-on: ${{ matrix.os }}
timeout-minutes: 15
steps:
- uses: actions/checkout@v3
- name: Cache node_modules
uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.os }}-${{ matrix.target }}-${{ env.NODE_VERSION }}-node_modules-${{ hashFiles('**/package-lock.json') }}
# Cargo already skips downloading dependencies if they already exist
- name: Cache cargo
uses: actions/cache@v3
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-${{ matrix.target }}-cargo-${{ hashFiles('**/Cargo.lock') }}
# Cache the `oxide` Rust build
- name: Cache oxide build
uses: actions/cache@v3
with:
path: |
./oxide/target/
./oxide/crates/node/*.node
./oxide/crates/node/index.js
./oxide/crates/node/index.d.ts
key: ${{ runner.os }}-${{ matrix.target }}-oxide-${{ hashFiles('./oxide/crates/**/*') }}
- name: Install Node.JS
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
- name: Setup rust target
run: rustup target add ${{ matrix.target }}
- name: Install dependencies
run: npm install
- name: Build release
run: cd ${{ env.OXIDE_LOCATION }} && npm run build
env:
RUST_TARGET: ${{ matrix.target }}
- name: Strip debug symbols # https://github.com/rust-lang/rust/issues/46034
if: ${{ matrix.strip }}
run: ${{ matrix.strip }} ${{ env.OXIDE_LOCATION }}/*.node
- name: Upload artifacts
uses: actions/upload-artifact@v3
with:
name: bindings-${{ matrix.target }}
path: |
${{ env.OXIDE_LOCATION }}/*.node
${{ matrix.binary }}
build-apple-silicon:
name: Build Apple Silicon (OXIDE)
runs-on: macos-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v3
- name: Cache node_modules
uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.os }}-${{ env.NODE_VERSION }}-node_modules-${{ hashFiles('**/package-lock.json') }}
# Cargo already skips downloading dependencies if they already exist
- name: Cache cargo
uses: actions/cache@v3
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
# Cache the `oxide` Rust build
- name: Cache oxide build
uses: actions/cache@v3
with:
path: |
./oxide/target/
./oxide/crates/node/*.node
./oxide/crates/node/index.js
./oxide/crates/node/index.d.ts
key: ${{ runner.os }}-oxide-${{ hashFiles('./oxide/crates/**/*') }}
- name: Install Node.JS
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
- name: Setup rust target
run: rustup target add aarch64-apple-darwin
- name: Install dependencies
run: npm install
- name: Build release
run: cd ${{ env.OXIDE_LOCATION }} && npm run build
env:
RUST_TARGET: aarch64-apple-darwin
JEMALLOC_SYS_WITH_LG_PAGE: 14
- name: Strip debug symbols # https://github.com/rust-lang/rust/issues/46034
run: strip -x ${{ env.OXIDE_LOCATION }}/*.node
- name: Upload artifacts
uses: actions/upload-artifact@v3
with:
name: bindings-aarch64-apple-darwin
path: |
${{ env.OXIDE_LOCATION }}/*.node
${{ env.APP_NAME }}
build-linux:
strategy:
fail-fast: false
matrix:
include:
- target: x86_64-unknown-linux-gnu
strip: strip
image: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian
- target: aarch64-unknown-linux-gnu
strip: llvm-strip
image: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian-aarch64
- target: armv7-unknown-linux-gnueabihf
strip: llvm-strip
image: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian-zig
- target: aarch64-unknown-linux-musl
image: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine
strip: aarch64-linux-musl-strip
- target: x86_64-unknown-linux-musl
image: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine
strip: strip
name: Build ${{ matrix.target }} (OXIDE)
runs-on: ubuntu-latest
timeout-minutes: 15
container:
image: ${{ matrix.image }}
steps:
- uses: actions/checkout@v3
- name: Cache node_modules
uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.os }}-${{ matrix.target }}-${{ env.NODE_VERSION }}-node_modules-${{ hashFiles('**/package-lock.json') }}
# Cargo already skips downloading dependencies if they already exist
- name: Cache cargo
uses: actions/cache@v3
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-${{ matrix.target }}-cargo-${{ hashFiles('**/Cargo.lock') }}
# Cache the `oxide` Rust build
- name: Cache oxide build
uses: actions/cache@v3
with:
path: |
./oxide/target/
./oxide/crates/node/*.node
./oxide/crates/node/index.js
./oxide/crates/node/index.d.ts
key: ${{ runner.os }}-${{ matrix.target }}-oxide-${{ hashFiles('./oxide/crates/**/*') }}
- name: Install Node.JS
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
- name: Setup cross compile toolchain
if: ${{ matrix.setup }}
run: ${{ matrix.setup }}
- name: Setup rust target
run: rustup target add ${{ matrix.target }}
- name: Install dependencies
run: npm install
- name: Build release
run: cd ${{ env.OXIDE_LOCATION }} && npm run build
env:
RUST_TARGET: ${{ matrix.target }}
- name: Strip debug symbols # https://github.com/rust-lang/rust/issues/46034
if: ${{ matrix.strip }}
run: ${{ matrix.strip }} ${{ env.OXIDE_LOCATION }}/*.node
- name: Upload artifacts
uses: actions/upload-artifact@v3
with:
name: bindings-${{ matrix.target }}
path: |
${{ env.OXIDE_LOCATION }}/*.node
${{ env.APP_NAME }}
release:
runs-on: ubuntu-latest
timeout-minutes: 15
name: Build and release
needs:
- build
- build-linux
- build-apple-silicon
steps:
- uses: actions/checkout@v3
- name: Cache node_modules
uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.os }}-${{ env.NODE_VERSION }}-node_modules-${{ hashFiles('**/package-lock.json') }}
# Cargo already skips downloading dependencies if they already exist
- name: Cache cargo
uses: actions/cache@v3
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
# Cache the `oxide` Rust build
- name: Cache oxide build
uses: actions/cache@v3
with:
path: |
./oxide/target/
./oxide/crates/node/*.node
./oxide/crates/node/index.js
./oxide/crates/node/index.d.ts
key: ${{ runner.os }}-oxide-${{ hashFiles('./oxide/crates/**/*') }}
- name: Install dependencies
run: npm install
- name: Calculate environment variables
run: |
echo "RELEASE_CHANNEL=$(node ./scripts/release-channel.js)" >> $GITHUB_ENV
echo "VERSION=$(node -e 'console.log(require(`./package.json`).version);')" >> $GITHUB_ENV
- name: Download artifacts
uses: actions/download-artifact@v3
with:
path: ${{ env.OXIDE_LOCATION }}
- run: echo //registry.npmjs.org/:_authToken=${NPM_TOKEN} > ~/.npmrc
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Move artifacts
run: |
cd ${{ env.OXIDE_LOCATION }}
cp bindings-aarch64-apple-darwin/oxide/crates/node/* ./npm/darwin-arm64/
cp bindings-aarch64-unknown-linux-gnu/oxide/crates/node/* ./npm/linux-arm64-gnu/
cp bindings-aarch64-unknown-linux-musl/oxide/crates/node/* ./npm/linux-arm64-musl/
cp bindings-armv7-unknown-linux-gnueabihf/oxide/crates/node/* ./npm/linux-arm-gnueabihf/
cp bindings-x86_64-apple-darwin/* ./npm/darwin-x64/
cp bindings-x86_64-pc-windows-msvc/* ./npm/win32-x64-msvc/
cp bindings-x86_64-unknown-linux-gnu/oxide/crates/node/* ./npm/linux-x64-gnu/
cp bindings-x86_64-unknown-linux-musl/oxide/crates/node/* ./npm/linux-x64-musl/
- name: Generate entry point
run: npm run build --prefix ${{ env.OXIDE_LOCATION }}
- name: Publish to npm
run: |
for pkg in ${{ env.OXIDE_LOCATION }}/npm/*; do
echo "Publishing $pkg..."
cd $pkg
npm version ${{ env.VERSION }} --force --no-git-tag-version
npm publish --tag ${{ env.RELEASE_CHANNEL }} --access public
cd -
done
echo "Publishing ${{ env.APP_NAME }}...";
cd ${{ env.OXIDE_LOCATION }}
npm version ${{ env.VERSION }} --force --no-git-tag-version
sed "s#\"0.0.0\"#\"${{ env.VERSION }}\"#g" package.json > package_updated.json
mv package_updated.json package.json
npm publish --tag ${{ env.RELEASE_CHANNEL }} --access public
cd -
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
tailwind-release:
runs-on: ubuntu-latest
timeout-minutes: 15
name: Build and release Tailwind CSS
needs:
- release
steps:
- uses: actions/checkout@v3
- name: Cache node_modules
uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.os }}-${{ matrix.target }}-${{ env.NODE_VERSION }}-node_modules-${{ hashFiles('**/package-lock.json') }}
# Cargo already skips downloading dependencies if they already exist
- name: Cache cargo
uses: actions/cache@v3
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-${{ matrix.target }}-cargo-${{ hashFiles('**/Cargo.lock') }}
# Cache the `oxide` Rust build
- name: Cache oxide build
uses: actions/cache@v3
with:
path: |
./oxide/target/
./oxide/crates/node/*.node
./oxide/crates/node/index.js
./oxide/crates/node/index.d.ts
key: ${{ runner.os }}-${{ matrix.target }}-oxide-${{ hashFiles('./oxide/crates/**/*') }}
- name: Use Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
registry-url: 'https://registry.npmjs.org'
- name: Calculate environment variables
run: |
echo "RELEASE_CHANNEL=$(node ./scripts/release-channel.js)" >> $GITHUB_ENV
echo "VERSION=$(node -e 'console.log(require(`./package.json`).version);')" >> $GITHUB_ENV
- name: Setup `@tailwindcss/oxide` version
run: |
sed "s#\"@tailwindcss/oxide\": \".*\"#\"@tailwindcss/oxide\": \"${{ env.VERSION }}\"#" package.json > package_updated.json
mv package_updated.json package.json
- name: Install dependencies
run: npm install
- name: Build Tailwind CSS
run: npm run build
- name: Test
run: |
npm test || \
npm test || \
npm test || exit 1
- name: 'Version: ${{ env.VERSION }}'
run: npm version ${{ env.VERSION }} --force --no-git-tag-version
- name: Publish
run: npm publish --tag ${{ env.RELEASE_CHANNEL }}
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
# - name: Trigger Tailwind Play update
# if: env.RELEASE_CHANNEL == 'latest'
# uses: actions/github-script@v6
# with:
# github-token: ${{ secrets.TAILWIND_PLAY_TOKEN }}
# script: |
# await github.rest.actions.createWorkflowDispatch({
# owner: 'tailwindlabs',
# repo: 'play.tailwindcss.com',
# ref: 'master',
# workflow_id: 'upgrade-tailwindcss.yml',
# inputs: {
# version: '${{ env.TAILWINDCSS_VERSION }}'
# }
# })

1
.gitignore vendored
View file

@ -1,4 +1,5 @@
/node_modules /node_modules
.github/**/node_modules
/coverage /coverage
/cli /cli
/lib /lib

6
.swcrc
View file

@ -2,12 +2,8 @@
"module": { "module": {
"type": "commonjs" "type": "commonjs"
}, },
"env": {
"targets": {
"node": "12.13.0"
}
},
"jsc": { "jsc": {
"target": "es2018",
"parser": { "parser": {
"syntax": "typescript", "syntax": "typescript",
"tsx": false "tsx": false

View file

@ -7,7 +7,104 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
- Nothing yet! ### Fixed
- Improve types for `resolveConfig` ([#12272](https://github.com/tailwindlabs/tailwindcss/pull/12272))
- Don’t add spaces to negative numbers following a comma ([#12324](https://github.com/tailwindlabs/tailwindcss/pull/12324))
- Don't emit `@config` in CSS when watching via the CLI ([#12327](https://github.com/tailwindlabs/tailwindcss/pull/12327))
- Ensure configured `font-feature-settings` for `mono` are included in Preflight ([#12342](https://github.com/tailwindlabs/tailwindcss/pull/12342))
- Improve candidate detection in minified JS arrays (without spaces) ([#12396](https://github.com/tailwindlabs/tailwindcss/pull/12396))
- Don't crash when given applying a variant to a negated version of a simple utility ([#12514](https://github.com/tailwindlabs/tailwindcss/pull/12514))
- Fix support for slashes in arbitrary modifiers ([#12515](https://github.com/tailwindlabs/tailwindcss/pull/12515))
- Fix source maps of variant utilities that come from an `@layer` rule ([#12508](https://github.com/tailwindlabs/tailwindcss/pull/12508))
- Fix loading of built-in plugins when using an ESM or TypeScript config with the Standalone CLI ([#12506](https://github.com/tailwindlabs/tailwindcss/pull/12506))
- [Oxide] Remove `autoprefixer` dependency ([#11315](https://github.com/tailwindlabs/tailwindcss/pull/11315))
- [Oxide] Fix source maps issue resulting in a crash ([#11319](https://github.com/tailwindlabs/tailwindcss/pull/11319))
- [Oxide] Fallback to RegEx based parser when using custom transformers or extractors ([#11335](https://github.com/tailwindlabs/tailwindcss/pull/11335))
- [Oxide] Bump `lightningcss` and reflect related improvements in tests ([#11550](https://github.com/tailwindlabs/tailwindcss/pull/11550))
### Added
- Add `svh`, `lvh`, and `dvh` values to default `height`/`min-height`/`max-height` theme ([#11317](https://github.com/tailwindlabs/tailwindcss/pull/11317))
- Add `has-*` variants for `:has(...)` pseudo-class ([#11318](https://github.com/tailwindlabs/tailwindcss/pull/11318))
- Add `text-wrap` utilities including `text-balance` and `text-pretty` ([#11320](https://github.com/tailwindlabs/tailwindcss/pull/11320), [#12031](https://github.com/tailwindlabs/tailwindcss/pull/12031))
- Extend default `opacity` scale to include all steps of 5 ([#11832](https://github.com/tailwindlabs/tailwindcss/pull/11832))
- Update Preflight `html` styles to include shadow DOM `:host` pseudo-class ([#11200](https://github.com/tailwindlabs/tailwindcss/pull/11200))
- Increase default values for `grid-rows-*` utilities from 1–6 to 1–12 ([#12180](https://github.com/tailwindlabs/tailwindcss/pull/12180))
- Add `size-*` utilities ([#12287](https://github.com/tailwindlabs/tailwindcss/pull/12287))
- Add utilities for CSS subgrid ([#12298](https://github.com/tailwindlabs/tailwindcss/pull/12298))
- Add spacing scale to `min-w-*`, `min-h-*`, and `max-w-*` utilities ([#12300](https://github.com/tailwindlabs/tailwindcss/pull/12300))
- Add `forced-color-adjust` utilities ([#11931](https://github.com/tailwindlabs/tailwindcss/pull/11931))
- Add `forced-colors` variant ([#11694](https://github.com/tailwindlabs/tailwindcss/pull/11694))
- Add `appearance-auto` utility ([#12404](https://github.com/tailwindlabs/tailwindcss/pull/12404))
- Add logical property values for `float` and `clear` utilities ([#12480](https://github.com/tailwindlabs/tailwindcss/pull/12480))
- [Oxide] New Rust template parsing engine ([#10252](https://github.com/tailwindlabs/tailwindcss/pull/10252))
- [Oxide] Support `@import "tailwindcss"` using top-level `index.css` file ([#11205](https://github.com/tailwindlabs/tailwindcss/pull/11205), ([#11260](https://github.com/tailwindlabs/tailwindcss/pull/11260)))
- [Oxide] Use `lightningcss` for nesting and vendor prefixes in PostCSS plugin ([#10399](https://github.com/tailwindlabs/tailwindcss/pull/10399))
- [Oxide] Automatically detect content paths when no `content` configuration is provided ([#11173](https://github.com/tailwindlabs/tailwindcss/pull/11173), [#11221](https://github.com/tailwindlabs/tailwindcss/pull/11221))
- [Oxide] Process and inline `@import` at-rules natively ([#11239](https://github.com/tailwindlabs/tailwindcss/pull/11239))
- [Oxide] Explicitly configure Lightning CSS features, and prefer user browserslist over default browserslist ([#11402](https://github.com/tailwindlabs/tailwindcss/pull/11402), [#11412](https://github.com/tailwindlabs/tailwindcss/pull/11412))
- [Oxide] Support loading plugins by package / file name ([#12087](https://github.com/tailwindlabs/tailwindcss/pull/12087))
### Changed
- Simplify the `sans` font-family stack ([#11748](https://github.com/tailwindlabs/tailwindcss/pull/11748))
- Disable the tap highlight overlay on iOS ([#12299](https://github.com/tailwindlabs/tailwindcss/pull/12299))
- [Oxide] Deprecate `--no-autoprefixer` flag in the CLI ([#11280](https://github.com/tailwindlabs/tailwindcss/pull/11280))
- [Oxide] Make the Rust based parser the default ([#11394](https://github.com/tailwindlabs/tailwindcss/pull/11394))
## [3.3.5] - 2023-10-25
### Fixed
- Fix incorrect spaces around `-` in `calc()` expression ([#12283](https://github.com/tailwindlabs/tailwindcss/pull/12283))
## [3.3.4] - 2023-10-24
### Fixed
- Improve normalisation of `calc()`-like functions ([#11686](https://github.com/tailwindlabs/tailwindcss/pull/11686))
- Skip `calc()` normalisation in nested `theme()` calls ([#11705](https://github.com/tailwindlabs/tailwindcss/pull/11705))
- Fix incorrectly generated CSS when using square brackets inside arbitrary properties ([#11709](https://github.com/tailwindlabs/tailwindcss/pull/11709))
- Make `content` optional for presets in TypeScript types ([#11730](https://github.com/tailwindlabs/tailwindcss/pull/11730))
- Handle variable colors that have variable fallback values ([#12049](https://github.com/tailwindlabs/tailwindcss/pull/12049))
- Batch reading content files to prevent `too many open files` error ([#12079](https://github.com/tailwindlabs/tailwindcss/pull/12079))
- Skip over classes inside `:not(…)` when nested in an at-rule ([#12105](https://github.com/tailwindlabs/tailwindcss/pull/12105))
- Update types to work with `Node16` module resolution ([#12097](https://github.com/tailwindlabs/tailwindcss/pull/12097))
- Don’t crash when important and parent selectors are equal in `@apply` ([#12112](https://github.com/tailwindlabs/tailwindcss/pull/12112))
- Eliminate irrelevant rules when applying variants ([#12113](https://github.com/tailwindlabs/tailwindcss/pull/12113))
- Improve RegEx parser, reduce possibilities as the key for arbitrary properties ([#12121](https://github.com/tailwindlabs/tailwindcss/pull/12121))
- Fix sorting of utilities that share multiple candidates ([#12173](https://github.com/tailwindlabs/tailwindcss/pull/12173))
- Ensure variants with arbitrary values and a modifier are correctly matched in the RegEx based parser ([#12179](https://github.com/tailwindlabs/tailwindcss/pull/12179))
- Fix crash when watching renamed files on FreeBSD ([#12193](https://github.com/tailwindlabs/tailwindcss/pull/12193))
- Allow plugins from a parent document to be used in an iframe ([#12208](https://github.com/tailwindlabs/tailwindcss/pull/12208))
- Add types for `tailwindcss/nesting` ([#12269](https://github.com/tailwindlabs/tailwindcss/pull/12269))
- Bump `jiti`, `fast-glob`, and `browserlist` dependencies ([#11550](https://github.com/tailwindlabs/tailwindcss/pull/11550))
- Improve automatic `var` injection for properties that accept a `<dashed-ident>` ([#12236](https://github.com/tailwindlabs/tailwindcss/pull/12236))
## [3.3.3] - 2023-07-13
### Fixed
- Fix issue where some pseudo-element variants generated the wrong selector ([#10943](https://github.com/tailwindlabs/tailwindcss/pull/10943), [#10962](https://github.com/tailwindlabs/tailwindcss/pull/10962), [#11111](https://github.com/tailwindlabs/tailwindcss/pull/11111))
- Make font settings propagate into buttons, inputs, etc. ([#10940](https://github.com/tailwindlabs/tailwindcss/pull/10940))
- Fix parsing of `theme()` inside `calc()` when there are no spaces around operators ([#11157](https://github.com/tailwindlabs/tailwindcss/pull/11157))
- Ensure `repeating-conic-gradient` is detected as an image ([#11180](https://github.com/tailwindlabs/tailwindcss/pull/11180))
- Move unknown pseudo-elements outside of `:is` by default ([#11345](https://github.com/tailwindlabs/tailwindcss/pull/11345))
- Escape animation names when prefixes contain special characters ([#11470](https://github.com/tailwindlabs/tailwindcss/pull/11470))
- Don't prefix arbitrary classes in `group` and `peer` variants ([#11454](https://github.com/tailwindlabs/tailwindcss/pull/11454))
- Sort classes using position of first matching rule ([#11504](https://github.com/tailwindlabs/tailwindcss/pull/11504))
- Allow variant to be an at-rule without a prelude ([#11589](https://github.com/tailwindlabs/tailwindcss/pull/11589))
- Make PostCSS plugin async to improve performance ([#11548](https://github.com/tailwindlabs/tailwindcss/pull/11548))
- Don’t error when a config file is missing ([f97759f](https://github.com/tailwindlabs/tailwindcss/commit/f97759f808d15ace66647b1405744fcf95a392e5))
### Added
- Add `aria-busy` utility ([#10966](https://github.com/tailwindlabs/tailwindcss/pull/10966))
### Changed
- Reset padding for `<dialog>` elements in preflight ([#11069](https://github.com/tailwindlabs/tailwindcss/pull/11069))
## [3.3.2] - 2023-04-25 ## [3.3.2] - 2023-04-25
@ -2236,7 +2333,10 @@ No release notes
- Everything! - Everything!
[unreleased]: https://github.com/tailwindlabs/tailwindcss/compare/v3.3.2...HEAD [unreleased]: https://github.com/tailwindlabs/tailwindcss/compare/v3.3.5...HEAD
[3.3.5]: https://github.com/tailwindlabs/tailwindcss/compare/v3.3.4...v3.3.5
[3.3.4]: https://github.com/tailwindlabs/tailwindcss/compare/v3.3.3...v3.3.4
[3.3.3]: https://github.com/tailwindlabs/tailwindcss/compare/v3.3.2...v3.3.3
[3.3.2]: https://github.com/tailwindlabs/tailwindcss/compare/v3.3.1...v3.3.2 [3.3.2]: https://github.com/tailwindlabs/tailwindcss/compare/v3.3.1...v3.3.2
[3.3.1]: https://github.com/tailwindlabs/tailwindcss/compare/v3.3.0...v3.3.1 [3.3.1]: https://github.com/tailwindlabs/tailwindcss/compare/v3.3.0...v3.3.1
[3.3.0]: https://github.com/tailwindlabs/tailwindcss/compare/v3.2.7...v3.3.0 [3.3.0]: https://github.com/tailwindlabs/tailwindcss/compare/v3.2.7...v3.3.0

View file

@ -14,7 +14,7 @@
<p align="center"> <p align="center">
<a href="https://github.com/tailwindlabs/tailwindcss/actions"><img src="https://img.shields.io/github/actions/workflow/status/tailwindlabs/tailwindcss/ci-stable.yml?branch=master" alt="Build Status"></a> <a href="https://github.com/tailwindlabs/tailwindcss/actions"><img src="https://img.shields.io/github/actions/workflow/status/tailwindlabs/tailwindcss/ci.yml?branch=master" alt="Build Status"></a>
<a href="https://www.npmjs.com/package/tailwindcss"><img src="https://img.shields.io/npm/dt/tailwindcss.svg" alt="Total Downloads"></a> <a href="https://www.npmjs.com/package/tailwindcss"><img src="https://img.shields.io/npm/dt/tailwindcss.svg" alt="Total Downloads"></a>
<a href="https://github.com/tailwindcss/tailwindcss/releases"><img src="https://img.shields.io/npm/v/tailwindcss.svg" alt="Latest Release"></a> <a href="https://github.com/tailwindcss/tailwindcss/releases"><img src="https://img.shields.io/npm/v/tailwindcss.svg" alt="Latest Release"></a>
<a href="https://github.com/tailwindcss/tailwindcss/blob/master/LICENSE"><img src="https://img.shields.io/npm/l/tailwindcss.svg" alt="License"></a> <a href="https://github.com/tailwindcss/tailwindcss/blob/master/LICENSE"><img src="https://img.shields.io/npm/l/tailwindcss.svg" alt="License"></a>

5
index.css Normal file
View file

@ -0,0 +1,5 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View file

@ -18,7 +18,7 @@
} }
}, },
"devDependencies": { "devDependencies": {
"postcss": "^8.4.23", "postcss": "^8.4.31",
"postcss-cli": "^10.1.0" "postcss-cli": "^10.1.0"
} }
} }

View file

@ -60,7 +60,7 @@ it('looks in the CWD by default', async () => {
let result = await build({ cwd: path.resolve(__dirname, '..') }) let result = await build({ cwd: path.resolve(__dirname, '..') })
expect(result.css).toMatchCss(css` expect(result.css).toMatchFormattedCss(css`
.content-\[real-static-positive\] { .content-\[real-static-positive\] {
--tw-content: real-static-positive; --tw-content: real-static-positive;
content: var(--tw-content); content: var(--tw-content);
@ -69,7 +69,7 @@ it('looks in the CWD by default', async () => {
result = await build({ cwd: path.resolve(__dirname, '../src') }) result = await build({ cwd: path.resolve(__dirname, '../src') })
expect(result.css).toMatchCss(``) expect(result.css).toMatchFormattedCss(``)
}) })
it('looks in the CWD for non-config-relative paths', async () => { it('looks in the CWD for non-config-relative paths', async () => {
@ -88,7 +88,7 @@ it('looks in the CWD for non-config-relative paths', async () => {
let result = await build({ cwd: path.resolve(__dirname, '..') }) let result = await build({ cwd: path.resolve(__dirname, '..') })
expect(result.css).toMatchCss(css` expect(result.css).toMatchFormattedCss(css`
.content-\[real-static-positive\] { .content-\[real-static-positive\] {
--tw-content: real-static-positive; --tw-content: real-static-positive;
content: var(--tw-content); content: var(--tw-content);
@ -97,7 +97,7 @@ it('looks in the CWD for non-config-relative paths', async () => {
result = await build({ cwd: path.resolve(__dirname, '../src') }) result = await build({ cwd: path.resolve(__dirname, '../src') })
expect(result.css).toMatchCss(``) expect(result.css).toMatchFormattedCss(``)
}) })
it('can look for content files relative to the config', async () => { it('can look for content files relative to the config', async () => {
@ -113,12 +113,12 @@ it('can look for content files relative to the config', async () => {
// Here `./real` doesn't exist next to the config in the root directory // Here `./real` doesn't exist next to the config in the root directory
let result = await build({ cwd: path.resolve(__dirname, '..') }) let result = await build({ cwd: path.resolve(__dirname, '..') })
expect(result.css).toMatchCss(css``) expect(result.css).toMatchFormattedCss(css``)
// But here it `./real` does exist next to the config in the `./src` directory! // But here it `./real` does exist next to the config in the `./src` directory!
result = await build({ cwd: path.resolve(__dirname, '../src') }) result = await build({ cwd: path.resolve(__dirname, '../src') })
expect(result.css).toMatchCss(css` expect(result.css).toMatchFormattedCss(css`
.content-\[real-static-positive\] { .content-\[real-static-positive\] {
--tw-content: real-static-positive; --tw-content: real-static-positive;
content: var(--tw-content); content: var(--tw-content);
@ -143,7 +143,7 @@ it('it handles ignored globs correctly when not relative to the config', async (
let result = await build({ cwd: path.resolve(__dirname, '..') }) let result = await build({ cwd: path.resolve(__dirname, '..') })
expect(result.css).toMatchCss(css` expect(result.css).toMatchFormattedCss(css`
.content-\[real-dynamic-positive\] { .content-\[real-dynamic-positive\] {
--tw-content: real-dynamic-positive; --tw-content: real-dynamic-positive;
content: var(--tw-content); content: var(--tw-content);
@ -157,7 +157,7 @@ it('it handles ignored globs correctly when not relative to the config', async (
// But here it `./real` does exist next to the config in the `./src` directory! // But here it `./real` does exist next to the config in the `./src` directory!
result = await build({ cwd: path.resolve(__dirname, '../src') }) result = await build({ cwd: path.resolve(__dirname, '../src') })
expect(result.css).toMatchCss(``) expect(result.css).toMatchFormattedCss(``)
}) })
it('it handles ignored globs correctly when relative to the config', async () => { it('it handles ignored globs correctly when relative to the config', async () => {
@ -177,12 +177,12 @@ it('it handles ignored globs correctly when relative to the config', async () =>
let result = await build({ cwd: path.resolve(__dirname, '..') }) let result = await build({ cwd: path.resolve(__dirname, '..') })
expect(result.css).toMatchCss(``) expect(result.css).toMatchFormattedCss(``)
// But here it `./real` does exist next to the config in the `./src` directory! // But here it `./real` does exist next to the config in the `./src` directory!
result = await build({ cwd: path.resolve(__dirname, '../src') }) result = await build({ cwd: path.resolve(__dirname, '../src') })
expect(result.css).toMatchCss(css` expect(result.css).toMatchFormattedCss(css`
.content-\[real-dynamic-positive\] { .content-\[real-dynamic-positive\] {
--tw-content: real-dynamic-positive; --tw-content: real-dynamic-positive;
content: var(--tw-content); content: var(--tw-content);
@ -215,7 +215,7 @@ it('it can resolve symlinks for files when not relative to the config', async ()
let result = await build({ cwd: path.resolve(__dirname, '..') }) let result = await build({ cwd: path.resolve(__dirname, '..') })
expect(result.css).toMatchCss(css` expect(result.css).toMatchFormattedCss(css`
.content-\[real-dynamic-positive\] { .content-\[real-dynamic-positive\] {
--tw-content: real-dynamic-positive; --tw-content: real-dynamic-positive;
content: var(--tw-content); content: var(--tw-content);
@ -237,7 +237,7 @@ it('it can resolve symlinks for files when not relative to the config', async ()
// But here it `./real` does exist next to the config in the `./src` directory! // But here it `./real` does exist next to the config in the `./src` directory!
result = await build({ cwd: path.resolve(__dirname, '../src') }) result = await build({ cwd: path.resolve(__dirname, '../src') })
expect(result.css).toMatchCss(``) expect(result.css).toMatchFormattedCss(``)
}) })
it('it can resolve symlinks for files when relative to the config', async () => { it('it can resolve symlinks for files when relative to the config', async () => {
@ -261,12 +261,12 @@ it('it can resolve symlinks for files when relative to the config', async () =>
let result = await build({ cwd: path.resolve(__dirname, '..') }) let result = await build({ cwd: path.resolve(__dirname, '..') })
expect(result.css).toMatchCss(``) expect(result.css).toMatchFormattedCss(``)
// But here it `./real` does exist next to the config in the `./src` directory! // But here it `./real` does exist next to the config in the `./src` directory!
result = await build({ cwd: path.resolve(__dirname, '../src') }) result = await build({ cwd: path.resolve(__dirname, '../src') })
expect(result.css).toMatchCss(css` expect(result.css).toMatchFormattedCss(css`
.content-\[real-dynamic-positive\] { .content-\[real-dynamic-positive\] {
--tw-content: real-dynamic-positive; --tw-content: real-dynamic-positive;
content: var(--tw-content); content: var(--tw-content);

View file

@ -5,10 +5,10 @@ let resolveToolRoot = require('./resolve-tool-root')
let SHOW_OUTPUT = false let SHOW_OUTPUT = false
let runningProcessess = [] let runningProcesses = []
afterEach(() => { afterEach(() => {
runningProcessess.splice(0).forEach((runningProcess) => runningProcess.stop()) runningProcesses.splice(0).forEach((runningProcess) => runningProcess.stop())
}) })
function debounce(fn, ms) { function debounce(fn, ms) {
@ -64,7 +64,7 @@ module.exports = function $(command, options = {}) {
messages.splice(0, idx + 1) messages.splice(0, idx + 1)
let actorIdx = actors.indexOf(next) let actorIdx = actors.indexOf(next)
actors.splice(actorIdx, 1) actors.splice(actorIdx, 1)
next.resolve() next.resolve(message)
break break
} }
} }
@ -129,7 +129,7 @@ module.exports = function $(command, options = {}) {
}) })
}) })
runningProcessess.push(runningProcess) runningProcesses.push(runningProcess)
return Object.assign(runningProcess, { return Object.assign(runningProcess, {
stop() { stop() {

View file

@ -5,6 +5,9 @@ let fs = require('fs/promises')
let chokidar = require('chokidar') let chokidar = require('chokidar')
let resolveToolRoot = require('./resolve-tool-root') let resolveToolRoot = require('./resolve-tool-root')
let FILE_STATE = {
NotFound: Symbol(),
}
function getWatcherOptions() { function getWatcherOptions() {
return { return {
@ -28,7 +31,7 @@ module.exports = function ({
cleanup = true, cleanup = true,
} = {}) { } = {}) {
let toolRoot = resolveToolRoot() let toolRoot = resolveToolRoot()
let fileCache = {} let fileCache = new Map()
let absoluteOutputFolder = path.resolve(toolRoot, output) let absoluteOutputFolder = path.resolve(toolRoot, output)
let absoluteInputFolder = path.resolve(toolRoot, input) let absoluteInputFolder = path.resolve(toolRoot, input)
@ -41,9 +44,9 @@ module.exports = function ({
// Restore all written files // Restore all written files
afterEach(async () => { afterEach(async () => {
await Promise.all( await Promise.all(
Object.entries(fileCache).map(async ([file, content]) => { Array.from(fileCache.entries()).map(async ([file, content]) => {
try { try {
if (content === null) { if (content === FILE_STATE.NotFound) {
return await fs.unlink(file) return await fs.unlink(file)
} else { } else {
return await fs.writeFile(file, content, 'utf8') return await fs.writeFile(file, content, 'utf8')
@ -89,7 +92,7 @@ module.exports = function ({
return { return {
cleanupFile(file) { cleanupFile(file) {
let filePath = path.resolve(toolRoot, file) let filePath = path.resolve(toolRoot, file)
fileCache[filePath] = null fileCache.set(filePath, FILE_STATE.NotFound)
}, },
async fileExists(file) { async fileExists(file) {
let filePath = path.resolve(toolRoot, file) let filePath = path.resolve(toolRoot, file)
@ -98,8 +101,11 @@ module.exports = function ({
async removeFile(file) { async removeFile(file) {
let filePath = path.resolve(toolRoot, file) let filePath = path.resolve(toolRoot, file)
if (!fileCache[filePath]) { if (!fileCache.has(filePath)) {
fileCache[filePath] = await fs.readFile(filePath, 'utf8').catch(() => null) fileCache.set(
filePath,
await fs.readFile(filePath, 'utf8').catch(() => FILE_STATE.NotFound)
)
} }
await fs.unlink(filePath).catch(() => null) await fs.unlink(filePath).catch(() => null)
@ -114,20 +120,31 @@ module.exports = function ({
}, },
async appendToInputFile(file, contents) { async appendToInputFile(file, contents) {
let filePath = path.resolve(absoluteInputFolder, file) let filePath = path.resolve(absoluteInputFolder, file)
if (!fileCache[filePath]) { if (!fileCache.has(filePath)) {
fileCache[filePath] = await fs.readFile(filePath, 'utf8') fileCache.set(filePath, await fs.readFile(filePath, 'utf8'))
} }
return fs.appendFile(filePath, contents, 'utf8') return fs.appendFile(filePath, contents, 'utf8')
}, },
async writeInputFile(file, contents) { async writeInputFile(file, contents) {
let filePath = path.resolve(absoluteInputFolder, file) let filePath = path.resolve(absoluteInputFolder, file)
if (!fileCache[filePath]) {
// Ensure the parent folder of the file exists
if (
!(await fs
.stat(filePath)
.then(() => true)
.catch(() => false))
) {
await fs.mkdir(path.dirname(filePath), { recursive: true })
}
if (!fileCache.has(filePath)) {
try { try {
fileCache[filePath] = await fs.readFile(filePath, 'utf8') fileCache.set(filePath, await fs.readFile(filePath, 'utf8'))
} catch (err) { } catch (err) {
if (err.code === 'ENOENT') { if (err.code === 'ENOENT') {
fileCache[filePath] = null // Sentinel value to `delete` the file afterwards. This also means that we are writing to a `new` file inside the test. fileCache.set(filePath, FILE_STATE.NotFound)
} else { } else {
throw err throw err
} }

View file

@ -19,6 +19,6 @@
} }
}, },
"devDependencies": { "devDependencies": {
"parcel": "^2.8.3" "parcel": "^2.10.3"
} }
} }

View file

@ -1,6 +1,5 @@
let $ = require('../../execute') let $ = require('../../execute')
let { css, html, javascript } = require('../../syntax') let { css, html, javascript } = require('../../syntax')
let { env } = require('../../../lib/lib/sharedState')
let { let {
appendToInputFile, appendToInputFile,
@ -40,7 +39,7 @@ describe('static build', () => {
'index.html', 'index.html',
html` html`
<link rel="stylesheet" href="./index.css" /> <link rel="stylesheet" href="./index.css" />
<div class="bg-primary"></div> <div class="z-primary"></div>
` `
) )
await writeInputFile( await writeInputFile(
@ -58,8 +57,8 @@ describe('static build', () => {
content: ['./src/index.html'], content: ['./src/index.html'],
theme: { theme: {
extend: { extend: {
colors: { zIndex: {
primary: 'black', primary: 0,
}, },
}, },
}, },
@ -74,26 +73,13 @@ describe('static build', () => {
env: { NODE_ENV: 'production' }, env: { NODE_ENV: 'production' },
}) })
if (env.ENGINE === 'stable') { expect(await readOutputFile(/index\.\w+\.css$/)).toIncludeCss(
expect(await readOutputFile(/index\.\w+\.css$/)).toIncludeCss( css`
css` .z-primary {
.bg-primary { z-index: 0;
--tw-bg-opacity: 1; }
background-color: rgb(0 0 0 / var(--tw-bg-opacity)); `
} )
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile(/index\.\w+\.css$/)).toIncludeCss(
css`
.bg-primary {
background-color: black;
}
`
)
}
}) })
it('can use a tailwind.config.ts configuration file', async () => { it('can use a tailwind.config.ts configuration file', async () => {
@ -102,7 +88,7 @@ describe('static build', () => {
'index.html', 'index.html',
html` html`
<link rel="stylesheet" href="./index.css" /> <link rel="stylesheet" href="./index.css" />
<div class="bg-primary"></div> <div class="z-primary"></div>
` `
) )
await writeInputFile( await writeInputFile(
@ -122,8 +108,8 @@ describe('static build', () => {
content: ['./src/index.html'], content: ['./src/index.html'],
theme: { theme: {
extend: { extend: {
colors: { zIndex: {
primary: 'black', primary: 0,
}, },
}, },
}, },
@ -138,26 +124,13 @@ describe('static build', () => {
env: { NODE_ENV: 'production' }, env: { NODE_ENV: 'production' },
}) })
if (env.ENGINE === 'stable') { expect(await readOutputFile(/index\.\w+\.css$/)).toIncludeCss(
expect(await readOutputFile(/index\.\w+\.css$/)).toIncludeCss( css`
css` .z-primary {
.bg-primary { z-index: 0;
--tw-bg-opacity: 1; }
background-color: rgb(0 0 0 / var(--tw-bg-opacity)); `
} )
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile(/index\.\w+\.css$/)).toIncludeCss(
css`
.bg-primary {
background-color: black;
}
`
)
}
}) })
}) })
@ -199,41 +172,22 @@ describe('watcher', () => {
) )
await waitForOutputFileChange(/index\.\w+\.css$/, async () => { await waitForOutputFileChange(/index\.\w+\.css$/, async () => {
await appendToInputFile('index.html', html`<div class="bg-red-500"></div>`) await appendToInputFile('index.html', html`<div class="z-0"></div>`)
}) })
if (env.ENGINE === 'stable') { expect(await readOutputFile(/index\.\w+\.css$/)).toIncludeCss(
expect(await readOutputFile(/index\.\w+\.css$/)).toIncludeCss( css`
css` .z-0 {
.bg-red-500 { z-index: 0;
--tw-bg-opacity: 1; }
background-color: rgb(239 68 68 / var(--tw-bg-opacity)); .font-bold {
} font-weight: 700;
.font-bold { }
font-weight: 700; .font-normal {
} font-weight: 400;
.font-normal { }
font-weight: 400; `
} )
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile(/index\.\w+\.css$/)).toIncludeCss(
css`
.bg-red-500 {
background-color: #ef4444;
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
}
return runningProcess.stop() return runningProcess.stop()
}) })
@ -271,41 +225,22 @@ describe('watcher', () => {
) )
await waitForOutputFileChange(/index\.\w+\.css$/, async () => { await waitForOutputFileChange(/index\.\w+\.css$/, async () => {
await appendToInputFile('glob/index.html', html`<div class="bg-red-500"></div>`) await appendToInputFile('glob/index.html', html`<div class="z-0"></div>`)
}) })
if (env.ENGINE === 'stable') { expect(await readOutputFile(/index\.\w+\.css$/)).toIncludeCss(
expect(await readOutputFile(/index\.\w+\.css$/)).toIncludeCss( css`
css` .z-0 {
.bg-red-500 { z-index: 0;
--tw-bg-opacity: 1; }
background-color: rgb(239 68 68 / var(--tw-bg-opacity)); .font-bold {
} font-weight: 700;
.font-bold { }
font-weight: 700; .font-normal {
} font-weight: 400;
.font-normal { }
font-weight: 400; `
} )
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile(/index\.\w+\.css$/)).toIncludeCss(
css`
.bg-red-500 {
background-color: #ef4444;
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
}
return runningProcess.stop() return runningProcess.stop()
}) })
@ -328,7 +263,7 @@ describe('watcher', () => {
.font-bold { .font-bold {
font-weight: 700; font-weight: 700;
} }
@media (min-width: 768px) { @media (width >= 768px) {
.md\:font-medium { .md\:font-medium {
font-weight: 500; font-weight: 500;
} }
@ -366,7 +301,7 @@ describe('watcher', () => {
.font-bold { .font-bold {
font-weight: bold; font-weight: bold;
} }
@media (min-width: 800px) { @media (width >= 800px) {
.md\:font-medium { .md\:font-medium {
font-weight: 500; font-weight: 500;
} }
@ -417,11 +352,11 @@ describe('watcher', () => {
expect(await readOutputFile(/index\.\w+\.css$/)).toIncludeCss( expect(await readOutputFile(/index\.\w+\.css$/)).toIncludeCss(
css` css`
/* prettier-ignore */
.btn { .btn {
border-radius: .25rem; border-radius: 0.25rem;
padding: .25rem .5rem; padding: 0.25rem 0.5rem;
} }
.font-bold { .font-bold {
font-weight: 700; font-weight: 700;
} }
@ -438,43 +373,25 @@ describe('watcher', () => {
@layer components { @layer components {
.btn { .btn {
@apply rounded bg-red-500 px-2 py-1; @apply rounded z-0 px-2 py-1;
} }
} }
` `
) )
}) })
if (env.ENGINE === 'stable') { expect(await readOutputFile(/index\.\w+\.css$/)).toIncludeCss(
expect(await readOutputFile(/index\.\w+\.css$/)).toIncludeCss( css`
css` .btn {
.btn { z-index: 0;
--tw-bg-opacity: 1; border-radius: 0.25rem;
background-color: rgb(239 68 68 / var(--tw-bg-opacity)); padding: 0.25rem 0.5rem;
border-radius: 0.25rem; }
padding: 0.25rem 0.5rem; .font-bold {
} font-weight: 700;
.font-bold { }
font-weight: 700; `
} )
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile(/index\.\w+\.css$/)).toIncludeCss(
css`
.btn {
background-color: #ef4444;
border-radius: 0.25rem;
padding: 0.25rem 0.5rem;
}
.font-bold {
font-weight: 700;
}
`
)
}
return runningProcess.stop() return runningProcess.stop()
}) })

View file

@ -18,7 +18,7 @@
} }
}, },
"devDependencies": { "devDependencies": {
"postcss": "^8.4.23", "postcss": "^8.4.31",
"postcss-cli": "^10.1.0" "postcss-cli": "^10.1.0"
} }
} }

View file

@ -1,6 +1,5 @@
let $ = require('../../execute') let $ = require('../../execute')
let { css, html, javascript } = require('../../syntax') let { css, html, javascript } = require('../../syntax')
let { env } = require('../../../lib/lib/sharedState')
let { readOutputFile, appendToInputFile, writeInputFile } = require('../../io')({ let { readOutputFile, appendToInputFile, writeInputFile } = require('../../io')({
output: 'dist', output: 'dist',
@ -58,41 +57,22 @@ describe('watcher', () => {
` `
) )
await appendToInputFile('index.html', html`<div class="bg-red-500"></div>`) await appendToInputFile('index.html', html`<div class="z-0"></div>`)
await runningProcess.onStderr(ready) await runningProcess.onStderr(ready)
if (env.ENGINE === 'stable') { expect(await readOutputFile('main.css')).toIncludeCss(
expect(await readOutputFile('main.css')).toIncludeCss( css`
css` .z-0 {
.bg-red-500 { z-index: 0;
--tw-bg-opacity: 1; }
background-color: rgb(239 68 68 / var(--tw-bg-opacity)); .font-bold {
} font-weight: 700;
.font-bold { }
font-weight: 700; .font-normal {
} font-weight: 400;
.font-normal { }
font-weight: 400; `
} )
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-red-500 {
background-color: #ef4444;
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
}
return runningProcess.stop() return runningProcess.stop()
}) })
@ -125,41 +105,22 @@ describe('watcher', () => {
` `
) )
await appendToInputFile('glob/index.html', html`<div class="bg-red-500"></div>`) await appendToInputFile('glob/index.html', html`<div class="z-0"></div>`)
await runningProcess.onStderr(ready) await runningProcess.onStderr(ready)
if (env.ENGINE === 'stable') { expect(await readOutputFile('main.css')).toIncludeCss(
expect(await readOutputFile('main.css')).toIncludeCss( css`
css` .z-0 {
.bg-red-500 { z-index: 0;
--tw-bg-opacity: 1; }
background-color: rgb(239 68 68 / var(--tw-bg-opacity)); .font-bold {
} font-weight: 700;
.font-bold { }
font-weight: 700; .font-normal {
} font-weight: 400;
.font-normal { }
font-weight: 400; `
} )
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-red-500 {
background-color: #ef4444;
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
}
return runningProcess.stop() return runningProcess.stop()
}) })
@ -187,23 +148,23 @@ describe('watcher', () => {
await writeInputFile( await writeInputFile(
'../tailwind.config.js', '../tailwind.config.js',
javascript` javascript`
module.exports = { module.exports = {
content: ['./src/index.html'], content: ['./src/index.html'],
theme: { theme: {
extend: { extend: {
screens: { screens: {
md: '800px' md: '800px'
},
fontWeight: {
bold: 'bold'
}
}, },
fontWeight: {
bold: 'bold'
}
}, },
corePlugins: { },
preflight: false, corePlugins: {
}, preflight: false,
plugins: [], },
} plugins: [],
}
` `
) )
await runningProcess.onStderr(ready) await runningProcess.onStderr(ready)
@ -277,47 +238,25 @@ describe('watcher', () => {
@layer components { @layer components {
.btn { .btn {
@apply rounded bg-red-500 px-2 py-1; @apply rounded z-0 px-2 py-1;
} }
} }
` `
) )
await runningProcess.onStderr(ready) await runningProcess.onStderr(ready)
if (env.ENGINE === 'stable') { expect(await readOutputFile('main.css')).toIncludeCss(
expect(await readOutputFile('main.css')).toIncludeCss( css`
css` .btn {
.btn { z-index: 0;
border-radius: 0.25rem; border-radius: 0.25rem;
--tw-bg-opacity: 1; padding: 0.25rem 0.5rem;
background-color: rgb(239 68 68 / var(--tw-bg-opacity)); }
padding-left: 0.5rem; .font-bold {
padding-right: 0.5rem; font-weight: 700;
padding-top: 0.25rem; }
padding-bottom: 0.25rem; `
} )
.font-bold {
font-weight: 700;
}
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
/* prettier-ignore */
.btn {
border-radius: 0.25rem;
background-color: #ef4444;
padding: 0.25rem 0.5rem;
}
.font-bold {
font-weight: 700;
}
`
)
}
return runningProcess.stop() return runningProcess.stop()
}) })

View file

@ -18,8 +18,8 @@
} }
}, },
"devDependencies": { "devDependencies": {
"rollup": "^3.21.0", "rollup": "^4.5.0",
"rollup-plugin-postcss": "^4.0.2", "rollup-plugin-postcss": "^4.0.2",
"sass": "^1.62.0" "sass": "^1.69.5"
} }
} }

View file

@ -1,6 +1,5 @@
let $ = require('../../execute') let $ = require('../../execute')
let { css, html, javascript } = require('../../syntax') let { css, html, javascript } = require('../../syntax')
let { env } = require('../../../lib/lib/sharedState')
let { readOutputFile, appendToInputFile, writeInputFile } = require('../../io')({ let { readOutputFile, appendToInputFile, writeInputFile } = require('../../io')({
output: 'dist', output: 'dist',
@ -58,41 +57,22 @@ describe('watcher', () => {
` `
) )
await appendToInputFile('index.html', html`<div class="bg-red-500"></div>`) await appendToInputFile('index.html', html`<div class="z-0"></div>`)
await runningProcess.onStderr(ready) await runningProcess.onStderr(ready)
if (env.ENGINE === 'stable') { expect(await readOutputFile('index.css')).toIncludeCss(
expect(await readOutputFile('index.css')).toIncludeCss( css`
css` .z-0 {
.bg-red-500 { z-index: 0;
--tw-bg-opacity: 1; }
background-color: rgb(239 68 68 / var(--tw-bg-opacity)); .font-bold {
} font-weight: 700;
.font-bold { }
font-weight: 700; .font-normal {
} font-weight: 400;
.font-normal { }
font-weight: 400; `
} )
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('index.css')).toIncludeCss(
css`
.bg-red-500 {
background-color: #ef4444;
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
}
return runningProcess.stop() return runningProcess.stop()
}) })
@ -125,41 +105,22 @@ describe('watcher', () => {
` `
) )
await appendToInputFile('glob/index.html', html`<div class="bg-red-500"></div>`) await appendToInputFile('glob/index.html', html`<div class="z-0"></div>`)
await runningProcess.onStderr(ready) await runningProcess.onStderr(ready)
if (env.ENGINE === 'stable') { expect(await readOutputFile('index.css')).toIncludeCss(
expect(await readOutputFile('index.css')).toIncludeCss( css`
css` .z-0 {
.bg-red-500 { z-index: 0;
--tw-bg-opacity: 1; }
background-color: rgb(239 68 68 / var(--tw-bg-opacity)); .font-bold {
} font-weight: 700;
.font-bold { }
font-weight: 700; .font-normal {
} font-weight: 400;
.font-normal { }
font-weight: 400; `
} )
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('index.css')).toIncludeCss(
css`
.bg-red-500 {
background-color: #ef4444;
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
}
return runningProcess.stop() return runningProcess.stop()
}) })
@ -275,43 +236,25 @@ describe('watcher', () => {
@layer components { @layer components {
.btn { .btn {
@apply rounded bg-red-500 px-2 py-1; @apply rounded z-0 px-2 py-1;
} }
} }
` `
) )
await runningProcess.onStderr(ready) await runningProcess.onStderr(ready)
if (env.ENGINE === 'stable') { expect(await readOutputFile('index.css')).toIncludeCss(
expect(await readOutputFile('index.css')).toIncludeCss( css`
css` .btn {
.btn { z-index: 0;
--tw-bg-opacity: 1; border-radius: 0.25rem;
background-color: rgb(239 68 68 / var(--tw-bg-opacity)); padding: 0.25rem 0.5rem;
border-radius: 0.25rem; }
padding: 0.25rem 0.5rem; .font-bold {
} font-weight: 700;
.font-bold { }
font-weight: 700; `
} )
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('index.css')).toIncludeCss(
css`
.btn {
background-color: #ef4444;
border-radius: 0.25rem;
padding: 0.25rem 0.5rem;
}
.font-bold {
font-weight: 700;
}
`
)
}
return runningProcess.stop() return runningProcess.stop()
}) })
@ -352,6 +295,8 @@ describe('watcher', () => {
) )
await runningProcess.onStderr(ready) await runningProcess.onStderr(ready)
await new Promise((resolve) => setTimeout(resolve, 5000))
expect(await readOutputFile('index.css')).toIncludeCss( expect(await readOutputFile('index.css')).toIncludeCss(
css` css`
.btn { .btn {
@ -370,44 +315,26 @@ describe('watcher', () => {
css` css`
@layer components { @layer components {
.btn { .btn {
@apply rounded bg-red-500 px-2 py-1; @apply rounded z-0 px-2 py-1;
} }
} }
` `
) )
await runningProcess.onStderr(ready) await runningProcess.onStderr(ready)
if (env.ENGINE === 'stable') { expect(await readOutputFile('index.css')).toIncludeCss(
expect(await readOutputFile('index.css')).toIncludeCss( css`
css` .btn {
.btn { z-index: 0;
--tw-bg-opacity: 1; border-radius: 0.25rem;
background-color: rgb(239 68 68 / var(--tw-bg-opacity)); padding: 0.25rem 0.5rem;
border-radius: 0.25rem; }
padding: 0.25rem 0.5rem; .font-bold {
} font-weight: 700;
.font-bold { }
font-weight: 700; `
} )
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('index.css')).toIncludeCss(
css`
.btn {
background-color: #ef4444;
border-radius: 0.25rem;
padding: 0.25rem 0.5rem;
}
.font-bold {
font-weight: 700;
}
`
)
}
return runningProcess.stop() return runningProcess.stop()
}) }, 30000)
}) })

View file

@ -18,7 +18,7 @@
} }
}, },
"devDependencies": { "devDependencies": {
"rollup": "^3.21.0", "rollup": "^4.5.0",
"rollup-plugin-postcss": "^4.0.2" "rollup-plugin-postcss": "^4.0.2"
} }
} }

View file

@ -1,6 +1,5 @@
let $ = require('../../execute') let $ = require('../../execute')
let { css, html, javascript } = require('../../syntax') let { css, html, javascript } = require('../../syntax')
let { env } = require('../../../lib/lib/sharedState')
let { readOutputFile, appendToInputFile, writeInputFile, removeFile } = require('../../io')({ let { readOutputFile, appendToInputFile, writeInputFile, removeFile } = require('../../io')({
output: 'dist', output: 'dist',
@ -30,7 +29,7 @@ describe('static build', () => {
it('can use a tailwind.config.js configuration file with ESM syntax', async () => { it('can use a tailwind.config.js configuration file with ESM syntax', async () => {
await removeFile('tailwind.config.js') await removeFile('tailwind.config.js')
await writeInputFile('index.html', html`<div class="bg-primary"></div>`) await writeInputFile('index.html', html`<div class="z-primary"></div>`)
await writeInputFile( await writeInputFile(
'index.css', 'index.css',
css` css`
@ -46,8 +45,8 @@ describe('static build', () => {
content: ['./src/index.html'], content: ['./src/index.html'],
theme: { theme: {
extend: { extend: {
colors: { zIndex: {
primary: 'black', primary: 0,
}, },
}, },
}, },
@ -62,31 +61,18 @@ describe('static build', () => {
env: { NODE_ENV: 'production' }, env: { NODE_ENV: 'production' },
}) })
if (env.ENGINE === 'stable') { expect(await readOutputFile('index.css')).toIncludeCss(
expect(await readOutputFile('index.css')).toIncludeCss( css`
css` .z-primary {
.bg-primary { z-index: 0;
--tw-bg-opacity: 1; }
background-color: rgb(0 0 0 / var(--tw-bg-opacity)); `
} )
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('index.css')).toIncludeCss(
css`
.bg-primary {
background-color: black;
}
`
)
}
}) })
it('can use a tailwind.config.ts configuration file', async () => { it('can use a tailwind.config.ts configuration file', async () => {
await removeFile('tailwind.config.js') await removeFile('tailwind.config.js')
await writeInputFile('index.html', html`<div class="bg-primary"></div>`) await writeInputFile('index.html', html`<div class="z-primary"></div>`)
await writeInputFile( await writeInputFile(
'index.css', 'index.css',
css` css`
@ -104,8 +90,8 @@ describe('static build', () => {
content: ['./src/index.html'], content: ['./src/index.html'],
theme: { theme: {
extend: { extend: {
colors: { zIndex: {
primary: 'black', primary: 0,
}, },
}, },
}, },
@ -120,26 +106,13 @@ describe('static build', () => {
env: { NODE_ENV: 'production' }, env: { NODE_ENV: 'production' },
}) })
if (env.ENGINE === 'stable') { expect(await readOutputFile('index.css')).toIncludeCss(
expect(await readOutputFile('index.css')).toIncludeCss( css`
css` .z-primary {
.bg-primary { z-index: 0;
--tw-bg-opacity: 1; }
background-color: rgb(0 0 0 / var(--tw-bg-opacity)); `
} )
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('index.css')).toIncludeCss(
css`
.bg-primary {
background-color: black;
}
`
)
}
}) })
}) })
@ -172,41 +145,22 @@ describe('watcher', () => {
` `
) )
await appendToInputFile('index.html', html`<div class="bg-red-500"></div>`) await appendToInputFile('index.html', html`<div class="z-0"></div>`)
await runningProcess.onStderr(ready) await runningProcess.onStderr(ready)
if (env.ENGINE === 'stable') { expect(await readOutputFile('index.css')).toIncludeCss(
expect(await readOutputFile('index.css')).toIncludeCss( css`
css` .z-0 {
.bg-red-500 { z-index: 0;
--tw-bg-opacity: 1; }
background-color: rgb(239 68 68 / var(--tw-bg-opacity)); .font-bold {
} font-weight: 700;
.font-bold { }
font-weight: 700; .font-normal {
} font-weight: 400;
.font-normal { }
font-weight: 400; `
} )
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('index.css')).toIncludeCss(
css`
.bg-red-500 {
background-color: #ef4444;
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
}
return runningProcess.stop() return runningProcess.stop()
}) })
@ -239,41 +193,22 @@ describe('watcher', () => {
` `
) )
await appendToInputFile('glob/index.html', html`<div class="bg-red-500"></div>`) await appendToInputFile('glob/index.html', html`<div class="z-0"></div>`)
await runningProcess.onStderr(ready) await runningProcess.onStderr(ready)
if (env.ENGINE === 'stable') { expect(await readOutputFile('index.css')).toIncludeCss(
expect(await readOutputFile('index.css')).toIncludeCss( css`
css` .z-0 {
.bg-red-500 { z-index: 0;
--tw-bg-opacity: 1; }
background-color: rgb(239 68 68 / var(--tw-bg-opacity)); .font-bold {
} font-weight: 700;
.font-bold { }
font-weight: 700; .font-normal {
} font-weight: 400;
.font-normal { }
font-weight: 400; `
} )
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('index.css')).toIncludeCss(
css`
.bg-red-500 {
background-color: #ef4444;
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
}
return runningProcess.stop() return runningProcess.stop()
}) })
@ -389,46 +324,25 @@ describe('watcher', () => {
@layer components { @layer components {
.btn { .btn {
@apply rounded bg-red-500 px-2 py-1; @apply rounded z-0 px-2 py-1;
} }
} }
` `
) )
await runningProcess.onStderr(ready) await runningProcess.onStderr(ready)
if (env.ENGINE === 'stable') { expect(await readOutputFile('index.css')).toIncludeCss(
expect(await readOutputFile('index.css')).toIncludeCss( css`
css` .btn {
.btn { z-index: 0;
border-radius: 0.25rem; border-radius: 0.25rem;
--tw-bg-opacity: 1; padding: 0.25rem 0.5rem;
background-color: rgb(239 68 68 / var(--tw-bg-opacity)); }
padding-left: 0.5rem; .font-bold {
padding-right: 0.5rem; font-weight: 700;
padding-top: 0.25rem; }
padding-bottom: 0.25rem; `
} )
.font-bold {
font-weight: 700;
}
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('index.css')).toIncludeCss(
css`
.btn {
border-radius: 0.25rem;
background-color: #ef4444;
padding: 0.25rem 0.5rem;
}
.font-bold {
font-weight: 700;
}
`
)
}
return runningProcess.stop() return runningProcess.stop()
}) })

View file

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View file

@ -0,0 +1 @@
<div class="font-bold"></div>

View file

@ -2,7 +2,6 @@ let path = require('path')
let $ = require('../../execute') let $ = require('../../execute')
let { css, html, javascript } = require('../../syntax') let { css, html, javascript } = require('../../syntax')
let resolveToolRoot = require('../../resolve-tool-root') let resolveToolRoot = require('../../resolve-tool-root')
let { env } = require('../../../lib/lib/sharedState')
let version = require('../../../package.json').version let version = require('../../../package.json').version
@ -19,7 +18,6 @@ let {
}) })
let EXECUTABLE = 'node ../../lib/cli.js' let EXECUTABLE = 'node ../../lib/cli.js'
let testStable = env.ENGINE === 'stable' ? test : test.skip
function dedent(input) { function dedent(input) {
let lines = input.split('\n') let lines = input.split('\n')
@ -98,26 +96,36 @@ describe('Build command', () => {
expect(withoutMinify.length).toBeGreaterThan(withMinify.length) expect(withoutMinify.length).toBeGreaterThan(withMinify.length)
}) })
testStable('--no-autoprefixer', async () => { // Deprecated
test('--no-autoprefixer (should produce a warning)', async () => {
await writeInputFile('index.html', html`<div class="select-none"></div>`) await writeInputFile('index.html', html`<div class="select-none"></div>`)
await writeInputFile(
'index.css',
css`
@tailwind utilities;
`
)
await $(`${EXECUTABLE} --output ./dist/main.css`) let runningProcess = $(
let withAutoprefixer = await readOutputFile('main.css') `${EXECUTABLE} --input ./src/index.css --output ./dist/main.css --no-autoprefixer`
)
let warning = runningProcess.onStderr((message) => message.includes('--no-autoprefixer'))
await runningProcess
expect(await warning).toMatchInlineSnapshot(`
"[deprecation] The --no-autoprefixer flag is deprecated and has no effect.
"
`)
let withoutAutoprefixer = await readOutputFile('main.css')
expect(withAutoprefixer).toIncludeCss(css` // This contains --webkit-user-select which may be strange, but it is expected because we are
// not handling the `--no-autoprefixer` flag anymore at all.
expect(withoutAutoprefixer).toMatchInlineSnapshot(`
"/* ! tailwindcss v3.3.2 | MIT License | https://tailwindcss.com */
.select-none { .select-none {
-webkit-user-select: none; -webkit-user-select: none;
user-select: none; user-select: none;
} }
`) "
await $(`${EXECUTABLE} --output ./dist/main.css --no-autoprefixer`)
let withoutAutoprefixer = await readOutputFile('main.css')
expect(withoutAutoprefixer).toIncludeCss(css`
.select-none {
user-select: none;
}
`) `)
}) })
@ -143,7 +151,7 @@ describe('Build command', () => {
theme: { theme: {
extend: { extend: {
fontWeight: { fontWeight: {
bold: 'BOLD', bold: 'bold',
}, },
}, },
}, },
@ -164,7 +172,7 @@ describe('Build command', () => {
expect(await readOutputFile('main.css')).toIncludeCss( expect(await readOutputFile('main.css')).toIncludeCss(
css` css`
.font-bold { .font-bold {
font-weight: BOLD; font-weight: bold;
} }
` `
) )
@ -184,7 +192,7 @@ describe('Build command', () => {
) )
}) })
testStable('--postcss (postcss.config.js)', async () => { test('--postcss (postcss.config.js)', async () => {
await writeInputFile('index.html', html`<div class="font-bold"></div>`) await writeInputFile('index.html', html`<div class="font-bold"></div>`)
let customConfig = javascript` let customConfig = javascript`
@ -196,7 +204,7 @@ describe('Build command', () => {
function before(root, result) { function before(root, result) {
// Inject a custom component with @apply rules to prove that we run // Inject a custom component with @apply rules to prove that we run
// this _before_ the actual tailwind plugin. // this _before_ the actual tailwind plugin.
let btn = postcss.parse('.btn { @apply bg-red-500 px-2 py-1 }') let btn = postcss.parse('.btn { @apply flex px-2 py-1 }')
root.append(btn.nodes) root.append(btn.nodes)
}, },
function tailwindcss() { function tailwindcss() {
@ -217,45 +225,21 @@ describe('Build command', () => {
await $(`${EXECUTABLE} --output ./dist/main.css --postcss`) await $(`${EXECUTABLE} --output ./dist/main.css --postcss`)
if (env.ENGINE === 'stable') { expect(await readOutputFile('main.css')).toIncludeCss(
expect(await readOutputFile('main.css')).toIncludeCss( css`
css` .font-bold-after {
.font-bold-after { font-weight: 700;
font-weight: 700; }
}
.btn-after { .btn-after {
--tw-bg-opacity: 1; padding: 0.25rem 0.5rem;
background-color: rgb(239 68 68 / var(--tw-bg-opacity)); display: flex;
padding-left: 0.5rem; }
padding-right: 0.5rem; `
padding-top: 0.25rem; )
padding-bottom: 0.25rem;
}
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.font-bold-after {
font-weight: 700;
}
.btn-after {
background-color: #ef4444;
padding-left: 0.5rem;
padding-right: 0.5rem;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
}
`
)
}
}) })
testStable('--postcss (custom.postcss.config.js)', async () => { test('--postcss (custom.postcss.config.js)', async () => {
await writeInputFile('index.html', html`<div class="font-bold"></div>`) await writeInputFile('index.html', html`<div class="font-bold"></div>`)
let customConfig = javascript` let customConfig = javascript`
@ -267,7 +251,7 @@ describe('Build command', () => {
function before(root, result) { function before(root, result) {
// Inject a custom component with @apply rules to prove that we run // Inject a custom component with @apply rules to prove that we run
// this _before_ the actual tailwind plugin. // this _before_ the actual tailwind plugin.
let btn = postcss.parse('.btn { @apply bg-red-500 px-2 py-1 }') let btn = postcss.parse('.btn { @apply flex px-2 py-1 }')
root.append(btn.nodes) root.append(btn.nodes)
}, },
function tailwindcss() { function tailwindcss() {
@ -288,45 +272,21 @@ describe('Build command', () => {
await $(`${EXECUTABLE} --output ./dist/main.css --postcss ./custom.postcss.config.js`) await $(`${EXECUTABLE} --output ./dist/main.css --postcss ./custom.postcss.config.js`)
if (env.ENGINE === 'stable') { expect(await readOutputFile('main.css')).toIncludeCss(
expect(await readOutputFile('main.css')).toIncludeCss( css`
css` .font-bold-after {
.font-bold-after { font-weight: 700;
font-weight: 700; }
}
.btn-after { .btn-after {
--tw-bg-opacity: 1; padding: 0.25rem 0.5rem;
background-color: rgb(239 68 68 / var(--tw-bg-opacity)); display: flex;
padding-left: 0.5rem; }
padding-right: 0.5rem; `
padding-top: 0.25rem; )
padding-bottom: 0.25rem;
}
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.font-bold-after {
font-weight: 700;
}
.btn-after {
background-color: #ef4444;
padding-left: 0.5rem;
padding-right: 0.5rem;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
}
`
)
}
}) })
testStable('--postcss supports process options', async () => { test('--postcss supports process options', async () => {
await writeInputFile('index.html', html`<div class="font-bold"></div>`) await writeInputFile('index.html', html`<div class="font-bold"></div>`)
let customConfig = javascript` let customConfig = javascript`
@ -360,7 +320,7 @@ describe('Build command', () => {
expect(contents).toContain(`/*# sourceMappingURL`) expect(contents).toContain(`/*# sourceMappingURL`)
}) })
testStable('--postcss supports process options with custom config', async () => { test('--postcss supports process options with custom config', async () => {
await writeInputFile('index.html', html`<div class="font-bold"></div>`) await writeInputFile('index.html', html`<div class="font-bold"></div>`)
let customConfig = javascript` let customConfig = javascript`
@ -472,7 +432,7 @@ describe('Build command', () => {
css` css`
@media (min-width: 768px) { @media (min-width: 768px) {
.md\:something-cool { .md\:something-cool {
color: blue; color: #00f;
} }
} }
` `
@ -481,10 +441,18 @@ describe('Build command', () => {
return runningProcess.stop() return runningProcess.stop()
}) })
testStable('postcss-import is included when using a custom postcss configuration', async () => { test('postcss-import is included when using a custom postcss configuration', async () => {
cleanupFile('src/test.css') cleanupFile('src/test.css')
await writeInputFile('index.html', html`<div class="md:something-cool"></div>`) await writeInputFile('index.html', html`<div class="md:something-cool"></div>`)
await writeInputFile(
'imported.css',
css`
.foo {
color: white;
}
`
)
await writeInputFile( await writeInputFile(
'test.css', 'test.css',
css` css`
@ -501,7 +469,9 @@ describe('Build command', () => {
expect(await readOutputFile('main.css')).toIncludeCss( expect(await readOutputFile('main.css')).toIncludeCss(
css` css`
@import './imported.css'; .foo {
color: #fff;
}
` `
) )
}) })
@ -509,28 +479,8 @@ describe('Build command', () => {
test('--help', async () => { test('--help', async () => {
let { combined } = await $(`${EXECUTABLE} --help`) let { combined } = await $(`${EXECUTABLE} --help`)
if (env.ENGINE === 'oxide') { expect(dedent(combined)).toEqual(
expect(dedent(combined)).toEqual( dedent(`
dedent(`
tailwindcss v${version}
Usage:
tailwindcss build [options]
Options:
-i, --input Input file
-o, --output Output file
-w, --watch Watch for changes and rebuild as needed
-p, --poll Use polling instead of filesystem events when watching
--content Content paths to use for removing unused classes
-m, --minify Minify the output
-c, --config Path to a custom config file
-h, --help Display usage information
`)
)
} else if (env.ENGINE === 'stable') {
expect(dedent(combined)).toEqual(
dedent(`
tailwindcss v${version} tailwindcss v${version}
Usage: Usage:
@ -545,11 +495,9 @@ describe('Build command', () => {
--postcss Load custom PostCSS configuration --postcss Load custom PostCSS configuration
-m, --minify Minify the output -m, --minify Minify the output
-c, --config Path to a custom config file -c, --config Path to a custom config file
--no-autoprefixer Disable autoprefixer
-h, --help Display usage information -h, --help Display usage information
`) `)
) )
}
}) })
}) })
@ -562,7 +510,6 @@ describe('Init command', () => {
{ flags: ['--ts', '--full'], name: 'tailwind.config.ts' }, { flags: ['--ts', '--full'], name: 'tailwind.config.ts' },
{ flags: ['--esm', '--full'], name: 'tailwind.config.js' }, { flags: ['--esm', '--full'], name: 'tailwind.config.js' },
])('works with all these flags: %j', async ({ flags, name }) => { ])('works with all these flags: %j', async ({ flags, name }) => {
cleanupFile(name)
await removeFile(name) await removeFile(name)
let { combined } = await $(`${EXECUTABLE} init ${flags.join(' ')}`) let { combined } = await $(`${EXECUTABLE} init ${flags.join(' ')}`)
@ -611,7 +558,7 @@ describe('Init command', () => {
expect((await readOutputFile('../full.config.js')).split('\n').length).toBeGreaterThan(50) expect((await readOutputFile('../full.config.js')).split('\n').length).toBeGreaterThan(50)
}) })
testStable('--postcss', async () => { test('--postcss', async () => {
expect(await fileExists('postcss.config.js')).toBe(true) expect(await fileExists('postcss.config.js')).toBe(true)
await removeFile('postcss.config.js') await removeFile('postcss.config.js')
expect(await fileExists('postcss.config.js')).toBe(false) expect(await fileExists('postcss.config.js')).toBe(false)
@ -631,24 +578,8 @@ describe('Init command', () => {
test('--help', async () => { test('--help', async () => {
let { combined } = await $(`${EXECUTABLE} init --help`) let { combined } = await $(`${EXECUTABLE} init --help`)
if (env.ENGINE === 'oxide') { expect(dedent(combined)).toEqual(
expect(dedent(combined)).toEqual( dedent(`
dedent(`
tailwindcss v${version}
Usage:
tailwindcss init [options]
Options:
--esm Initialize configuration file as ESM
--ts Initialize configuration file as TypeScript
-f, --full Include the default values for all options in the generated configuration file
-h, --help Display usage information
`)
)
} else if (env.ENGINE === 'stable') {
expect(dedent(combined)).toEqual(
dedent(`
tailwindcss v${version} tailwindcss v${version}
Usage: Usage:
@ -661,12 +592,10 @@ describe('Init command', () => {
-f, --full Include the default values for all options in the generated configuration file -f, --full Include the default values for all options in the generated configuration file
-h, --help Display usage information -h, --help Display usage information
`) `)
) )
}
}) })
test('ESM config is created by default in an ESM project', async () => { test('ESM config is created by default in an ESM project', async () => {
cleanupFile('tailwind.config.js')
await removeFile('tailwind.config.js') await removeFile('tailwind.config.js')
let pkg = await readOutputFile('../package.json') let pkg = await readOutputFile('../package.json')
@ -696,7 +625,6 @@ describe('Init command', () => {
}) })
test('CJS config is created by default in a non-ESM project', async () => { test('CJS config is created by default in a non-ESM project', async () => {
cleanupFile('tailwind.config.js')
await removeFile('tailwind.config.js') await removeFile('tailwind.config.js')
let pkg = await readOutputFile('../package.json') let pkg = await readOutputFile('../package.json')

File diff suppressed because it is too large Load diff

View file

@ -21,6 +21,6 @@
}, },
"devDependencies": { "devDependencies": {
"isomorphic-fetch": "^3.0.0", "isomorphic-fetch": "^3.0.0",
"vite": "^4.3.2" "vite": "^5.0.2"
} }
} }

View file

@ -2,7 +2,6 @@ require('isomorphic-fetch')
let $ = require('../../execute') let $ = require('../../execute')
let { css, html, javascript } = require('../../syntax') let { css, html, javascript } = require('../../syntax')
let { env } = require('../../../lib/lib/sharedState')
let { readOutputFile, appendToInputFile, writeInputFile, removeFile } = require('../../io')({ let { readOutputFile, appendToInputFile, writeInputFile, removeFile } = require('../../io')({
output: 'dist', output: 'dist',
@ -48,7 +47,7 @@ describe('static build', () => {
'index.html', 'index.html',
html` html`
<link rel="stylesheet" href="./index.css" /> <link rel="stylesheet" href="./index.css" />
<div class="bg-primary"></div> <div class="z-primary"></div>
` `
) )
await removeFile('tailwind.config.js') await removeFile('tailwind.config.js')
@ -59,8 +58,8 @@ describe('static build', () => {
content: ['index.html'], content: ['index.html'],
theme: { theme: {
extend: { extend: {
colors: { zIndex: {
primary: 'black', primary: 0,
}, },
}, },
}, },
@ -75,26 +74,13 @@ describe('static build', () => {
env: { NODE_ENV: 'production', NO_COLOR: '1' }, env: { NODE_ENV: 'production', NO_COLOR: '1' },
}) })
if (env.ENGINE === 'stable') { expect(await readOutputFile(/index.\w+\.css$/)).toIncludeCss(
expect(await readOutputFile(/index.\w+\.css$/)).toIncludeCss( css`
css` .z-primary {
.bg-primary { z-index: 0;
--tw-bg-opacity: 1; }
background-color: rgb(0 0 0 / var(--tw-bg-opacity)); `
} )
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile(/index.\w+\.css$/)).toIncludeCss(
css`
.bg-primary {
background-color: black;
}
`
)
}
}) })
it('can use a tailwind.config.ts configuration file', async () => { it('can use a tailwind.config.ts configuration file', async () => {
@ -102,7 +88,7 @@ describe('static build', () => {
'index.html', 'index.html',
html` html`
<link rel="stylesheet" href="./index.css" /> <link rel="stylesheet" href="./index.css" />
<div class="bg-primary"></div> <div class="z-primary"></div>
` `
) )
await removeFile('tailwind.config.js') await removeFile('tailwind.config.js')
@ -115,8 +101,8 @@ describe('static build', () => {
content: ['index.html'], content: ['index.html'],
theme: { theme: {
extend: { extend: {
colors: { zIndex: {
primary: 'black', primary: 0,
}, },
}, },
}, },
@ -131,26 +117,13 @@ describe('static build', () => {
env: { NODE_ENV: 'production', NO_COLOR: '1' }, env: { NODE_ENV: 'production', NO_COLOR: '1' },
}) })
if (env.ENGINE === 'stable') { expect(await readOutputFile(/index.\w+\.css$/)).toIncludeCss(
expect(await readOutputFile(/index.\w+\.css$/)).toIncludeCss( css`
css` .z-primary {
.bg-primary { z-index: 0;
--tw-bg-opacity: 1; }
background-color: rgb(0 0 0 / var(--tw-bg-opacity)); `
} )
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile(/index.\w+\.css$/)).toIncludeCss(
css`
.bg-primary {
background-color: black;
}
`
)
}
}) })
}) })
@ -191,41 +164,22 @@ describe('watcher', () => {
` `
) )
await appendToInputFile('index.html', html`<div class="bg-red-500"></div>`) await appendToInputFile('index.html', html`<div class="z-0"></div>`)
await runningProcess.onStdout((message) => message.includes('page reload')) await runningProcess.onStdout((message) => message.includes('page reload'))
if (env.ENGINE === 'stable') { expect(await fetchCSS()).toIncludeCss(
expect(await fetchCSS()).toIncludeCss( css`
css` .z-0 {
.bg-red-500 { z-index: 0;
--tw-bg-opacity: 1; }
background-color: rgb(239 68 68 / var(--tw-bg-opacity)); .font-bold {
} font-weight: 700;
.font-bold { }
font-weight: 700; .font-normal {
} font-weight: 400;
.font-normal { }
font-weight: 400; `
} )
`
)
}
if (env.ENGINE === 'oxide') {
expect(await fetchCSS()).toIncludeCss(
css`
.bg-red-500 {
background-color: #ef4444;
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
}
return runningProcess.stop() return runningProcess.stop()
}) })
@ -262,41 +216,22 @@ describe('watcher', () => {
` `
) )
await appendToInputFile('glob/index.html', html`<div class="bg-red-500"></div>`) await appendToInputFile('glob/index.html', html`<div class="z-0"></div>`)
await runningProcess.onStdout((message) => message.includes('page reload')) await runningProcess.onStdout((message) => message.includes('page reload'))
if (env.ENGINE === 'stable') { expect(await fetchCSS()).toIncludeCss(
expect(await fetchCSS()).toIncludeCss( css`
css` .z-0 {
.bg-red-500 { z-index: 0;
--tw-bg-opacity: 1; }
background-color: rgb(239 68 68 / var(--tw-bg-opacity)); .font-bold {
} font-weight: 700;
.font-bold { }
font-weight: 700; .font-normal {
} font-weight: 400;
.font-normal { }
font-weight: 400; `
} )
`
)
}
if (env.ENGINE === 'oxide') {
expect(await fetchCSS()).toIncludeCss(
css`
.bg-red-500 {
background-color: #ef4444;
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
}
return runningProcess.stop() return runningProcess.stop()
}) })
@ -428,49 +363,25 @@ describe('watcher', () => {
@layer components { @layer components {
.btn { .btn {
@apply rounded bg-red-500 px-2 py-1; @apply rounded z-0 px-2 py-1;
} }
} }
` `
) )
await runningProcess.onStdout((message) => message.includes('hmr update /index.css')) await runningProcess.onStdout((message) => message.includes('hmr update /index.css'))
if (env.ENGINE === 'stable') { expect(await fetchCSS()).toIncludeCss(
expect(await fetchCSS()).toIncludeCss( css`
css` .btn {
.btn { z-index: 0;
border-radius: 0.25rem; border-radius: 0.25rem;
--tw-bg-opacity: 1; padding: 0.25rem 0.5rem;
background-color: rgb(239 68 68 / var(--tw-bg-opacity)); }
padding-left: 0.5rem; .font-bold {
padding-right: 0.5rem; font-weight: 700;
padding-top: 0.25rem; }
padding-bottom: 0.25rem; `
} )
.font-bold {
font-weight: 700;
}
`
)
}
if (env.ENGINE === 'oxide') {
expect(await fetchCSS()).toIncludeCss(
css`
.btn {
border-radius: 0.25rem;
background-color: #ef4444;
padding-left: 0.5rem;
padding-right: 0.5rem;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
}
.font-bold {
font-weight: 700;
}
`
)
}
return runningProcess.stop() return runningProcess.stop()
}) })

View file

@ -1,6 +1,5 @@
let $ = require('../../execute') let $ = require('../../execute')
let { css, html, javascript } = require('../../syntax') let { css, html, javascript } = require('../../syntax')
let { env } = require('../../../lib/lib/sharedState')
let { let {
appendToInputFile, appendToInputFile,
@ -28,7 +27,7 @@ describe('static build', () => {
it('can use a tailwind.config.js configuration file with ESM syntax', async () => { it('can use a tailwind.config.js configuration file with ESM syntax', async () => {
await removeFile('tailwind.config.js') await removeFile('tailwind.config.js')
await writeInputFile('index.html', html`<div class="bg-primary"></div>`) await writeInputFile('index.html', html`<div class="z-primary"></div>`)
await writeInputFile( await writeInputFile(
'index.css', 'index.css',
css` css`
@ -44,8 +43,8 @@ describe('static build', () => {
content: ['./src/index.html'], content: ['./src/index.html'],
theme: { theme: {
extend: { extend: {
colors: { zIndex: {
primary: 'black', primary: 0,
}, },
}, },
}, },
@ -58,31 +57,18 @@ describe('static build', () => {
await $('webpack --mode=production') await $('webpack --mode=production')
if (env.ENGINE === 'stable') { expect(await readOutputFile('main.css')).toIncludeCss(
expect(await readOutputFile('main.css')).toIncludeCss( css`
css` .z-primary {
.bg-primary { z-index: 0;
--tw-bg-opacity: 1; }
background-color: rgb(0 0 0 / var(--tw-bg-opacity)); `
} )
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-primary {
background-color: black;
}
`
)
}
}) })
it('can use a tailwind.config.ts configuration file', async () => { it('can use a tailwind.config.ts configuration file', async () => {
await removeFile('tailwind.config.js') await removeFile('tailwind.config.js')
await writeInputFile('index.html', html`<div class="bg-primary"></div>`) await writeInputFile('index.html', html`<div class="z-primary"></div>`)
await writeInputFile( await writeInputFile(
'index.css', 'index.css',
css` css`
@ -100,8 +86,8 @@ describe('static build', () => {
content: ['./src/index.html'], content: ['./src/index.html'],
theme: { theme: {
extend: { extend: {
colors: { zIndex: {
primary: 'black', primary: 0,
}, },
}, },
}, },
@ -114,26 +100,13 @@ describe('static build', () => {
await $('webpack --mode=production') await $('webpack --mode=production')
if (env.ENGINE === 'stable') { expect(await readOutputFile('main.css')).toIncludeCss(
expect(await readOutputFile('main.css')).toIncludeCss( css`
css` .z-primary {
.bg-primary { z-index: 0;
--tw-bg-opacity: 1; }
background-color: rgb(0 0 0 / var(--tw-bg-opacity)); `
} )
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-primary {
background-color: black;
}
`
)
}
}) })
}) })
@ -169,41 +142,22 @@ describe('watcher', () => {
) )
await waitForOutputFileChange('main.css', async () => { await waitForOutputFileChange('main.css', async () => {
await appendToInputFile('index.html', html`<div class="bg-red-500"></div>`) await appendToInputFile('index.html', html`<div class="z-0"></div>`)
}) })
if (env.ENGINE === 'stable') { expect(await readOutputFile('main.css')).toIncludeCss(
expect(await readOutputFile('main.css')).toIncludeCss( css`
css` .z-0 {
.bg-red-500 { z-index: 0;
--tw-bg-opacity: 1; }
background-color: rgb(239 68 68 / var(--tw-bg-opacity)); .font-bold {
} font-weight: 700;
.font-bold { }
font-weight: 700; .font-normal {
} font-weight: 400;
.font-normal { }
font-weight: 400; `
} )
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-red-500 {
background-color: #ef4444;
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
}
return runningProcess.stop() return runningProcess.stop()
}) })
@ -239,41 +193,22 @@ describe('watcher', () => {
) )
await waitForOutputFileChange('main.css', async () => { await waitForOutputFileChange('main.css', async () => {
await appendToInputFile('glob/index.html', html`<div class="bg-red-500"></div>`) await appendToInputFile('glob/index.html', html`<div class="z-0"></div>`)
}) })
if (env.ENGINE === 'stable') { expect(await readOutputFile('main.css')).toIncludeCss(
expect(await readOutputFile('main.css')).toIncludeCss( css`
css` .z-0 {
.bg-red-500 { z-index: 0;
--tw-bg-opacity: 1; }
background-color: rgb(239 68 68 / var(--tw-bg-opacity)); .font-bold {
} font-weight: 700;
.font-bold { }
font-weight: 700; .font-normal {
} font-weight: 400;
.font-normal { }
font-weight: 400; `
} )
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-red-500 {
background-color: #ef4444;
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
}
return runningProcess.stop() return runningProcess.stop()
}) })
@ -394,46 +329,25 @@ describe('watcher', () => {
@layer components { @layer components {
.btn { .btn {
@apply rounded bg-red-500 px-2 py-1; @apply rounded z-0 px-2 py-1;
} }
} }
` `
) )
}) })
if (env.ENGINE === 'stable') { expect(await readOutputFile('main.css')).toIncludeCss(
expect(await readOutputFile('main.css')).toIncludeCss( css`
css` .btn {
.btn { z-index: 0;
border-radius: 0.25rem; border-radius: 0.25rem;
--tw-bg-opacity: 1; padding: 0.25rem 0.5rem;
background-color: rgb(239 68 68 / var(--tw-bg-opacity)); }
padding-left: 0.5rem; .font-bold {
padding-right: 0.5rem; font-weight: 700;
padding-top: 0.25rem; }
padding-bottom: 0.25rem; `
} )
.font-bold {
font-weight: 700;
}
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.btn {
background-color: #ef4444;
border-radius: 0.25rem;
padding: 0.25rem 0.5rem;
}
.font-bold {
font-weight: 700;
}
`
)
}
return runningProcess.stop() return runningProcess.stop()
}) })

View file

@ -1,6 +1,5 @@
let $ = require('../../execute') let $ = require('../../execute')
let { css, html, javascript } = require('../../syntax') let { css, html, javascript } = require('../../syntax')
let { env } = require('../../../lib/lib/sharedState')
let { let {
appendToInputFile, appendToInputFile,
@ -28,7 +27,7 @@ describe('static build', () => {
it('can use a tailwind.config.js configuration file with ESM syntax', async () => { it('can use a tailwind.config.js configuration file with ESM syntax', async () => {
await removeFile('tailwind.config.js') await removeFile('tailwind.config.js')
await writeInputFile('index.html', html`<div class="bg-primary"></div>`) await writeInputFile('index.html', html`<div class="z-primary"></div>`)
await writeInputFile( await writeInputFile(
'index.css', 'index.css',
css` css`
@ -44,8 +43,8 @@ describe('static build', () => {
content: ['./src/index.html'], content: ['./src/index.html'],
theme: { theme: {
extend: { extend: {
colors: { zIndex: {
primary: 'black', primary: 0,
}, },
}, },
}, },
@ -58,31 +57,18 @@ describe('static build', () => {
await $('webpack --mode=production') await $('webpack --mode=production')
if (env.ENGINE === 'stable') { expect(await readOutputFile('main.css')).toIncludeCss(
expect(await readOutputFile('main.css')).toIncludeCss( css`
css` .z-primary {
.bg-primary { z-index: 0;
--tw-bg-opacity: 1; }
background-color: rgb(0 0 0 / var(--tw-bg-opacity)); `
} )
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-primary {
background-color: black;
}
`
)
}
}) })
it('can use a tailwind.config.ts configuration file', async () => { it('can use a tailwind.config.ts configuration file', async () => {
await removeFile('tailwind.config.js') await removeFile('tailwind.config.js')
await writeInputFile('index.html', html`<div class="bg-primary"></div>`) await writeInputFile('index.html', html`<div class="z-primary"></div>`)
await writeInputFile( await writeInputFile(
'index.css', 'index.css',
css` css`
@ -100,8 +86,8 @@ describe('static build', () => {
content: ['./src/index.html'], content: ['./src/index.html'],
theme: { theme: {
extend: { extend: {
colors: { zIndex: {
primary: 'black', primary: 0,
}, },
}, },
}, },
@ -114,26 +100,13 @@ describe('static build', () => {
await $('webpack --mode=production') await $('webpack --mode=production')
if (env.ENGINE === 'stable') { expect(await readOutputFile('main.css')).toIncludeCss(
expect(await readOutputFile('main.css')).toIncludeCss( css`
css` .z-primary {
.bg-primary { z-index: 0;
--tw-bg-opacity: 1; }
background-color: rgb(0 0 0 / var(--tw-bg-opacity)); `
} )
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-primary {
background-color: black;
}
`
)
}
}) })
}) })
@ -169,41 +142,22 @@ describe('watcher', () => {
) )
await waitForOutputFileChange('main.css', async () => { await waitForOutputFileChange('main.css', async () => {
await appendToInputFile('index.html', html`<div class="bg-red-500"></div>`) await appendToInputFile('index.html', html`<div class="z-0"></div>`)
}) })
if (env.ENGINE === 'stable') { expect(await readOutputFile('main.css')).toIncludeCss(
expect(await readOutputFile('main.css')).toIncludeCss( css`
css` .z-0 {
.bg-red-500 { z-index: 0;
--tw-bg-opacity: 1; }
background-color: rgb(239 68 68 / var(--tw-bg-opacity)); .font-bold {
} font-weight: 700;
.font-bold { }
font-weight: 700; .font-normal {
} font-weight: 400;
.font-normal { }
font-weight: 400; `
} )
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-red-500 {
background-color: #ef4444;
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
}
return runningProcess.stop() return runningProcess.stop()
}) })
@ -239,41 +193,22 @@ describe('watcher', () => {
) )
await waitForOutputFileChange('main.css', async () => { await waitForOutputFileChange('main.css', async () => {
await appendToInputFile('index.html', html`<div class="bg-red-500"></div>`) await appendToInputFile('index.html', html`<div class="z-0"></div>`)
}) })
if (env.ENGINE === 'stable') { expect(await readOutputFile('main.css')).toIncludeCss(
expect(await readOutputFile('main.css')).toIncludeCss( css`
css` .z-0 {
.bg-red-500 { z-index: 0;
--tw-bg-opacity: 1; }
background-color: rgb(239 68 68 / var(--tw-bg-opacity)); .font-bold {
} font-weight: 700;
.font-bold { }
font-weight: 700; .font-normal {
} font-weight: 400;
.font-normal { }
font-weight: 400; `
} )
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-red-500 {
background-color: #ef4444;
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
}
return runningProcess.stop() return runningProcess.stop()
}) })
@ -394,46 +329,25 @@ describe('watcher', () => {
@layer components { @layer components {
.btn { .btn {
@apply rounded bg-red-500 px-2 py-1; @apply rounded z-0 px-2 py-1;
} }
} }
` `
) )
}) })
if (env.ENGINE === 'stable') { expect(await readOutputFile('main.css')).toIncludeCss(
expect(await readOutputFile('main.css')).toIncludeCss( css`
css` .btn {
.btn { z-index: 0;
border-radius: 0.25rem; border-radius: 0.25rem;
--tw-bg-opacity: 1; padding: 0.25rem 0.5rem;
background-color: rgb(239 68 68 / var(--tw-bg-opacity)); }
padding-left: 0.5rem; .font-bold {
padding-right: 0.5rem; font-weight: 700;
padding-top: 0.25rem; }
padding-bottom: 0.25rem; `
} )
.font-bold {
font-weight: 700;
}
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.btn {
background-color: #ef4444;
border-radius: 0.25rem;
padding: 0.25rem 0.5rem;
}
.font-bold {
font-weight: 700;
}
`
)
}
return runningProcess.stop() return runningProcess.stop()
}) })
@ -447,7 +361,7 @@ describe('watcher', () => {
content: { content: {
files: ['./src/index.html'], files: ['./src/index.html'],
}, },
safelist: ['bg-red-500','bg-red-600'], safelist: ['z-0', 'z-10'],
theme: { theme: {
extend: { extend: {
}, },
@ -464,43 +378,21 @@ describe('watcher', () => {
await waitForOutputFileCreation('main.css') await waitForOutputFileCreation('main.css')
if (env.ENGINE === 'stable') { expect(await readOutputFile('main.css')).toIncludeCss(
expect(await readOutputFile('main.css')).toIncludeCss( css`
css` .z-0 {
.bg-red-500 { z-index: 0;
--tw-bg-opacity: 1; }
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
}
.bg-red-600 { .z-10 {
--tw-bg-opacity: 1; z-index: 10;
background-color: rgb(220 38 38 / var(--tw-bg-opacity)); }
}
.font-bold { .font-bold {
font-weight: 700; font-weight: 700;
} }
` `
) )
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-red-500 {
background-color: #ef4444;
}
.bg-red-600 {
background-color: #dc2626;
}
.font-bold {
font-weight: 700;
}
`
)
}
return runningProcess.stop() return runningProcess.stop()
}) })

View file

@ -1,7 +1,11 @@
const prettier = require('prettier') const prettier = require('prettier')
const { diff } = require('jest-diff') const { diff } = require('jest-diff')
const lightningcss = require('lightningcss')
const log = require('../src/util/log').default const log = require('../src/util/log').default
const { version } = require('../package.json')
function license() {
return `/* ! tailwindcss v${version} | MIT License | https://tailwindcss.com */\n`
}
let warn let warn
@ -22,46 +26,7 @@ function formatPrettier(input) {
} }
function format(input) { function format(input) {
try { return formatPrettier(input).replace(/\n{2,}/g, '\n')
return lightningcss
.transform({
filename: 'input.css',
code: Buffer.from(input),
minify: false,
targets: { chrome: 106 << 16 },
drafts: {
nesting: true,
customMedia: true,
},
})
.code.toString('utf8')
} catch (err) {
try {
// Lightning CSS is pretty strict, so it will fail for `@media screen(md) {}` for example,
// in that case we can fallback to prettier since it doesn't really care. However if an
// actual syntax error is made, then we still want to show the proper error.
return formatPrettier(input.replace(/\n/g, ''))
} catch {
let lines = err.source.split('\n')
let e = new Error(
[
'Error formatting using Lightning CSS:',
'',
...[
'```css',
...lines.slice(Math.max(err.loc.line - 3, 0), err.loc.line),
' '.repeat(err.loc.column - 1) + '^-- ' + err.toString(),
...lines.slice(err.loc.line, err.loc.line + 2),
'```',
],
].join('\n')
)
if (Error.captureStackTrace) {
Error.captureStackTrace(e, toMatchFormattedCss)
}
throw e
}
}
} }
function toMatchFormattedCss(received = '', argument = '') { function toMatchFormattedCss(received = '', argument = '') {
@ -71,6 +36,11 @@ function toMatchFormattedCss(received = '', argument = '') {
promise: this.promise, promise: this.promise,
} }
// Drop the license from the tests such that we can purely focus on the actual CSS being
// generated.
received = received.replace(license(), '')
argument = argument.replace(license(), '')
let formattedReceived = format(received) let formattedReceived = format(received)
let formattedArgument = format(argument) let formattedArgument = format(argument)
@ -86,10 +56,7 @@ function toMatchFormattedCss(received = '', argument = '') {
) )
} }
: () => { : () => {
let actual = formatPrettier(formattedReceived).replace(/\n\n/g, '\n') let diffString = diff(formattedArgument, formattedReceived, {
let expected = formatPrettier(formattedArgument).replace(/\n\n/g, '\n')
let diffString = diff(expected, actual, {
expand: this.expand, expand: this.expand,
}) })
@ -98,8 +65,8 @@ function toMatchFormattedCss(received = '', argument = '') {
'\n\n' + '\n\n' +
(diffString && diffString.includes('- Expect') (diffString && diffString.includes('- Expect')
? `Difference:\n\n${diffString}` ? `Difference:\n\n${diffString}`
: `Expected: ${this.utils.printExpected(expected)}\n` + : `Expected: ${this.utils.printExpected(formattedArgument)}\n` +
`Received: ${this.utils.printReceived(actual)}`) `Received: ${this.utils.printReceived(formattedReceived)}`)
) )
} }
@ -107,13 +74,10 @@ function toMatchFormattedCss(received = '', argument = '') {
} }
expect.extend({ expect.extend({
// Compare two CSS strings with all whitespace removed
// This is probably naive but it's fast and works well enough.
toMatchCss: toMatchFormattedCss,
toMatchFormattedCss: toMatchFormattedCss, toMatchFormattedCss: toMatchFormattedCss,
toIncludeCss(received, argument) { toIncludeCss(received, argument) {
let options = { let options = {
comment: 'stripped(received).includes(stripped(argument))', comment: 'formatCSS(received).includes(formatCSS(argument))',
isNot: this.isNot, isNot: this.isNot,
promise: this.promise, promise: this.promise,
} }

4
nesting/index.d.ts vendored Normal file
View file

@ -0,0 +1,4 @@
import type { AcceptedPlugin, PluginCreator } from 'postcss'
declare const plugin: PluginCreator<AcceptedPlugin | string | void>
export = plugin

687
oxide/Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -105,7 +105,7 @@ fn main() -> Result<(), std::io::Error> {
let candidates = content_paths.par_bridge().flat_map(|path| { let candidates = content_paths.par_bridge().flat_map(|path| {
read_lines(path) read_lines(path)
.unwrap() .unwrap()
.filter_map(Result::ok) .map_while(Result::ok)
.par_bridge() .par_bridge()
.flat_map_iter(|line| { .flat_map_iter(|line| {
Extractor::unique(line.as_bytes(), Default::default()) Extractor::unique(line.as_bytes(), Default::default())

View file

@ -12,14 +12,17 @@ fxhash = "0.2.1"
crossbeam = "0.8.2" crossbeam = "0.8.2"
tracing = { version = "0.1.37", features = [] } tracing = { version = "0.1.37", features = [] }
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] } tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
walkdir = "2.3.3"
ignore = "0.4.20"
[dev-dependencies] [dev-dependencies]
criterion = { version = "0.3", features = ['html_reports'] } criterion = { version = "0.3", features = ['html_reports'] }
tempfile = "3.5.0"
[[bench]] [[bench]]
name = "parse_candidates" name = "parse_candidates"
harness = false harness = false
[[bench]] [[bench]]
name = "parse_candidate_strings_from_files" name = "parse_candidate_strings"
harness = false harness = false

View file

@ -1,6 +1,6 @@
use criterion::{criterion_group, criterion_main, Criterion}; use criterion::{criterion_group, criterion_main, Criterion};
use std::path::PathBuf; use std::path::PathBuf;
use tailwindcss_core::{parse_candidate_strings_from_files, ChangedContent}; use tailwindcss_core::{parse_candidate_strings, ChangedContent, Parsing, IO};
pub fn criterion_benchmark(c: &mut Criterion) { pub fn criterion_benchmark(c: &mut Criterion) {
// current_dir will be set to ./crates/core // current_dir will be set to ./crates/core
@ -11,7 +11,6 @@ pub fn criterion_benchmark(c: &mut Criterion) {
let mut all_files: Vec<(u64, PathBuf)> = std::fs::read_dir(fixtures_path) let mut all_files: Vec<(u64, PathBuf)> = std::fs::read_dir(fixtures_path)
.unwrap() .unwrap()
.into_iter()
.filter_map(Result::ok) .filter_map(Result::ok)
.map(|dir_entry| dir_entry.path()) .map(|dir_entry| dir_entry.path())
.filter(|path| path.is_file()) .filter(|path| path.is_file())
@ -51,8 +50,13 @@ pub fn criterion_benchmark(c: &mut Criterion) {
}) })
.collect(); .collect();
c.bench_function("parse_candidate_strings_from_files", |b| { c.bench_function("parse_candidate_strings", |b| {
b.iter(|| parse_candidate_strings_from_files(changed_content.clone())) b.iter(|| {
parse_candidate_strings(
changed_content.clone(),
Parsing::Parallel as u8 | IO::Parallel as u8,
)
})
}); });
} }

View file

@ -31,6 +31,18 @@ pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("parse_candidate_strings (real world)", |b| { c.bench_function("parse_candidate_strings (real world)", |b| {
b.iter(|| parse(include_bytes!("./fixtures/template-499.html"))) b.iter(|| parse(include_bytes!("./fixtures/template-499.html")))
}); });
let mut group = c.benchmark_group("sample-size-example");
group.sample_size(10);
group.bench_function("parse_candidate_strings (fast space skipping)", |b| {
let count = 10_000;
let crazy1 = format!("{}underline", " ".repeat(count));
let crazy2 = crazy1.repeat(count);
let crazy3 = crazy2.as_bytes();
b.iter(|| parse(black_box(crazy3)))
});
} }
criterion_group!(benches, criterion_benchmark); criterion_group!(benches, criterion_benchmark);

View file

@ -22,7 +22,6 @@ impl Candidate {
x == ':' && !in_arbitrary x == ':' && !in_arbitrary
} }
}) })
.into_iter()
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let utility = match parts.pop() { let utility = match parts.pop() {

View file

@ -0,0 +1,159 @@
use std::{ascii::escape_default, fmt::Display};
#[derive(Debug, Clone)]
pub struct Cursor<'a> {
// The input we're scanning
pub input: &'a [u8],
// The location of the cursor in the input
pub pos: usize,
/// Is the cursor at the start of the input
pub at_start: bool,
/// Is the cursor at the end of the input
pub at_end: bool,
/// The previously consumed character
/// If `at_start` is true, this will be NUL
pub prev: u8,
/// The current character
pub curr: u8,
/// The upcoming character (if any)
/// If `at_end` is true, this will be NUL
pub next: u8,
}
impl<'a> Cursor<'a> {
pub fn new(input: &'a [u8]) -> Self {
let mut cursor = Self {
input,
pos: 0,
at_start: true,
at_end: false,
prev: 0x00,
curr: 0x00,
next: 0x00,
};
cursor.move_to(0);
cursor
}
pub fn rewind_by(&mut self, amount: usize) {
self.move_to(self.pos.saturating_sub(amount));
}
pub fn advance_by(&mut self, amount: usize) {
self.move_to(self.pos.saturating_add(amount));
}
pub fn move_to(&mut self, pos: usize) {
let len = self.input.len();
let pos = pos.clamp(0, len);
self.pos = pos;
self.at_start = pos == 0;
self.at_end = pos + 1 >= len;
self.prev = if pos > 0 { self.input[pos - 1] } else { 0x00 };
self.curr = if pos < len { self.input[pos] } else { 0x00 };
self.next = if pos + 1 < len {
self.input[pos + 1]
} else {
0x00
};
}
}
impl<'a> Display for Cursor<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let len = self.input.len().to_string();
let pos = format!("{: >len_count$}", self.pos, len_count = len.len());
write!(f, "{}/{} ", pos, len)?;
if self.at_start {
write!(f, "S ")?;
} else if self.at_end {
write!(f, "E ")?;
} else {
write!(f, "M ")?;
}
fn to_str(c: u8) -> String {
if c == 0x00 {
"NUL".into()
} else {
format!("{:?}", escape_default(c).to_string())
}
}
write!(
f,
"[{} {} {}]",
to_str(self.prev),
to_str(self.curr),
to_str(self.next)
)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_cursor() {
let mut cursor = Cursor::new(b"hello world");
assert_eq!(cursor.pos, 0);
assert!(cursor.at_start);
assert!(!cursor.at_end);
assert_eq!(cursor.prev, 0x00);
assert_eq!(cursor.curr, b'h');
assert_eq!(cursor.next, b'e');
cursor.advance_by(1);
assert_eq!(cursor.pos, 1);
assert!(!cursor.at_start);
assert!(!cursor.at_end);
assert_eq!(cursor.prev, b'h');
assert_eq!(cursor.curr, b'e');
assert_eq!(cursor.next, b'l');
// Advancing too far should stop at the end
cursor.advance_by(10);
assert_eq!(cursor.pos, 11);
assert!(!cursor.at_start);
assert!(cursor.at_end);
assert_eq!(cursor.prev, b'd');
assert_eq!(cursor.curr, 0x00);
assert_eq!(cursor.next, 0x00);
// Can't advance past the end
cursor.advance_by(1);
assert_eq!(cursor.pos, 11);
assert!(!cursor.at_start);
assert!(cursor.at_end);
assert_eq!(cursor.prev, b'd');
assert_eq!(cursor.curr, 0x00);
assert_eq!(cursor.next, 0x00);
cursor.rewind_by(1);
assert_eq!(cursor.pos, 10);
assert!(!cursor.at_start);
assert!(cursor.at_end);
assert_eq!(cursor.prev, b'l');
assert_eq!(cursor.curr, b'd');
assert_eq!(cursor.next, 0x00);
cursor.rewind_by(10);
assert_eq!(cursor.pos, 0);
assert!(cursor.at_start);
assert!(!cursor.at_end);
assert_eq!(cursor.prev, 0x00);
assert_eq!(cursor.curr, b'h');
assert_eq!(cursor.next, b'e');
}
}

View file

@ -0,0 +1,89 @@
use crate::cursor::Cursor;
const STRIDE: usize = 16;
type Mask = [bool; STRIDE];
#[inline(always)]
pub fn fast_skip(cursor: &Cursor) -> Option<usize> {
// If we don't have enough bytes left to check then bail early
if cursor.pos + STRIDE >= cursor.input.len() {
return None;
}
if !cursor.curr.is_ascii_whitespace() {
return None;
}
let mut offset = 1;
// SAFETY: We've already checked (indirectly) that this index is valid
let remaining = unsafe { cursor.input.get_unchecked(cursor.pos..) };
// NOTE: This loop uses primitives designed to be auto-vectorized
// Do not change this loop without benchmarking the results
// And checking the generated assembly using godbolt.org
for (i, chunk) in remaining.chunks_exact(STRIDE).enumerate() {
let value = load(chunk);
let is_whitespace = is_ascii_whitespace(value);
let is_all_whitespace = all_true(is_whitespace);
if is_all_whitespace {
offset = (i + 1) * STRIDE;
} else {
break;
}
}
Some(cursor.pos + offset)
}
#[inline(always)]
fn load(input: &[u8]) -> [u8; STRIDE] {
let mut value = [0u8; STRIDE];
value.copy_from_slice(input);
value
}
#[inline(always)]
fn eq(input: [u8; STRIDE], val: u8) -> Mask {
let mut res = [false; STRIDE];
for n in 0..STRIDE {
res[n] = input[n] == val
}
res
}
#[inline(always)]
fn or(a: [bool; STRIDE], b: [bool; STRIDE]) -> [bool; STRIDE] {
let mut res = [false; STRIDE];
for n in 0..STRIDE {
res[n] = a[n] | b[n];
}
res
}
#[inline(always)]
fn all_true(a: [bool; STRIDE]) -> bool {
let mut res = true;
for item in a.iter().take(STRIDE) {
res &= item;
}
res
}
#[inline(always)]
fn is_ascii_whitespace(value: [u8; STRIDE]) -> [bool; STRIDE] {
let whitespace_1 = eq(value, b'\t');
let whitespace_2 = eq(value, b'\n');
let whitespace_3 = eq(value, b'\x0C');
let whitespace_4 = eq(value, b'\r');
let whitespace_5 = eq(value, b' ');
or(
or(
or(or(whitespace_1, whitespace_2), whitespace_3),
whitespace_4,
),
whitespace_5,
)
}

View file

@ -0,0 +1,259 @@
3dm
3ds
3g2
3gp
7z
DS_Store
a
aac
adp
ai
aif
aiff
alz
ape
apk
appimage
ar
arj
asf
au
avi
bak
baml
bh
bin
bk
bmp
btif
bz2
bzip2
cab
caf
cgm
class
cmx
cpio
cr2
cur
dat
dcm
deb
dex
djvu
dll
dmg
dng
doc
docm
docx
dot
dotm
dra
dsk
dts
dtshd
dvb
dwg
dxf
ecelp4800
ecelp7470
ecelp9600
egg
eol
eot
epub
exe
f4v
fbs
fh
fla
flac
flatpak
fli
flv
fpx
fst
fvt
g3
gh
gif
graffle
gz
gzip
h261
h263
h264
icns
ico
ief
img
ipa
iso
jar
jpeg
jpg
jpgv
jpm
jxr
key
ktx
lha
lib
lockb
lvp
lz
lzh
lzma
lzo
m3u
m4a
m4v
mar
mdi
mht
mid
midi
mj2
mka
mkv
mmr
mng
mobi
mov
movie
mp3
mp4
mp4a
mpeg
mpg
mpga
mxu
nef
npx
numbers
nupkg
o
odp
ods
odt
oga
ogg
ogv
otf
ott
pages
pbm
pcx
pdb
pdf
pea
pgm
pic
png
pnm
pot
potm
potx
ppa
ppam
ppm
pps
ppsm
ppsx
ppt
pptm
pptx
psd
pya
pyc
pyo
pyv
qt
rar
ras
raw
resources
rgb
rip
rlc
rmf
rmvb
rpm
rtf
rz
s3m
s7z
scpt
sgi
shar
sil
sketch
slk
smv
snap
snk
so
stl
sub
suo
swf
tar
tbz
tbz2
tga
tgz
thmx
tif
tiff
tlz
ttc
ttf
txz
udf
uvh
uvi
uvm
uvp
uvs
uvu
viv
vob
war
wav
wax
wbmp
wdp
weba
webm
webp
whl
wim
wm
wma
wmv
wmx
woff
woff2
wrm
wvx
xbm
xif
xla
xlam
xls
xlsb
xlsm
xlsx
xlt
xltm
xltx
xm
xmind
xpi
xpm
xwd
xz
z
zip
zipx

View file

@ -0,0 +1,6 @@
css
less
lock
sass
scss
styl

View file

@ -0,0 +1,3 @@
package-lock.json
pnpm-lock.yaml
bun.lockb

View file

@ -0,0 +1,57 @@
# HTML
html
pug
# JS
astro
cjs
cts
jade
js
jsx
mjs
mts
svelte
ts
tsx
vue
# Markdown
md
mdx
# ASP
aspx
razor
# Handlebars
handlebars
hbs
mustache
# PHP
php
twig
# Ruby
erb
haml
liquid
rb
rhtml
slim
# Elixir / Phoenix
eex
heex
# Nunjucks
njk
nunjucks
# Python
py
tpl
# Rust
rs

View file

@ -12,7 +12,6 @@ pub fn fast_glob(
.follow_links(true) .follow_links(true)
.build() .build()
.unwrap() .unwrap()
.into_iter()
.filter_map(Result::ok) .filter_map(Result::ok)
.map(|file| file.path().to_path_buf()) .map(|file| file.path().to_path_buf())
})) }))

View file

@ -1,9 +1,16 @@
use crate::parser::Extractor; use crate::parser::Extractor;
use fxhash::FxHashSet;
use ignore::WalkBuilder;
use rayon::prelude::*; use rayon::prelude::*;
use std::cmp::Ordering;
use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
use tracing::event; use tracing::event;
use walkdir::WalkDir;
pub mod candidate; pub mod candidate;
pub mod cursor;
pub mod fast_skip;
pub mod glob; pub mod glob;
pub mod location; pub mod location;
pub mod modifier; pub mod modifier;
@ -11,14 +18,7 @@ pub mod parser;
pub mod utility; pub mod utility;
pub mod variant; pub mod variant;
#[derive(Debug, Clone)] fn init_tracing() {
pub struct ChangedContent {
pub file: Option<PathBuf>,
pub content: Option<String>,
pub extension: String,
}
pub fn parse_candidate_strings_from_files(changed_content: Vec<ChangedContent>) -> Vec<String> {
if matches!(std::env::var("DEBUG"), Ok(value) if value.eq("*") || value.eq("1") || value.eq("true") || value.contains("tailwind")) if matches!(std::env::var("DEBUG"), Ok(value) if value.eq("*") || value.eq("1") || value.eq("true") || value.contains("tailwind"))
{ {
tracing_subscriber::fmt() tracing_subscriber::fmt()
@ -27,8 +27,325 @@ pub fn parse_candidate_strings_from_files(changed_content: Vec<ChangedContent>)
.compact() .compact()
.init(); .init();
} }
}
parse_all_blobs(read_all_files(changed_content)) #[derive(Debug, Clone)]
pub struct ChangedContent {
pub file: Option<PathBuf>,
pub content: Option<String>,
pub extension: String,
}
#[derive(Debug, Clone)]
pub struct ContentPathInfo {
pub base: String,
}
pub fn resolve_content_paths(args: ContentPathInfo) -> Vec<String> {
let root = Path::new(&args.base);
let allowed_paths = FxHashSet::from_iter(
WalkBuilder::new(root)
.hidden(false)
.filter_entry(|entry| match entry.file_type() {
Some(file_type) if file_type.is_dir() => entry
.file_name()
.to_str()
.map(|s| s != ".git")
.unwrap_or(false),
_ => true,
})
.build()
.filter_map(Result::ok)
.map(|x| x.into_path()),
);
// A list of directory names where we can't use globs, but we should track each file
// individually instead. This is because these directories are often used for both source and
// destination files.
let mut forced_static_directories = vec![root.join("public")];
// A list of known extensions + a list of extensions we found in the project.
let mut found_extensions = FxHashSet::from_iter(
include_str!("fixtures/template-extensions.txt")
.trim()
.lines()
.filter(|x| !x.starts_with('#')) // Drop commented lines
.filter(|x| !x.is_empty()) // Drop empty lines
.map(|x| x.to_string()),
);
// A list of static files that we should track directly.
let mut static_files = vec![];
// All root directories.
let mut root_directories = FxHashSet::from_iter(vec![root.to_path_buf()]);
// All directories where we can safely use deeply nested globs to watch all files.
// In other comments we refer to these as "deep glob directories" or similar.
//
// E.g.: ./src/**/*.{html,js}
let mut deep_globable_directories: FxHashSet<PathBuf> = FxHashSet::default();
// All directories where we can only use shallow globs to watch all direct files but not
// folders.
// In other comments we refer to these as "shallow glob directories" or similar.
//
// E.g.: ./src/*/*.{html,js}
let mut shallow_globable_directories: FxHashSet<PathBuf> = FxHashSet::default();
// Collect all valid paths from the root. This will already filter out ignored files, unknown
// extensions and binary files.
let mut it = WalkDir::new(root)
// Sorting to make sure that we always see the directories before the files. Also sorting
// alphabetically by default.
.sort_by(
|a, z| match (a.file_type().is_dir(), z.file_type().is_dir()) {
(true, false) => Ordering::Less,
(false, true) => Ordering::Greater,
_ => a.file_name().cmp(z.file_name()),
},
)
.into_iter();
loop {
// We are only interested in valid entries
let entry = match it.next() {
Some(Ok(entry)) => entry,
_ => break,
};
// Ignore known directories that we don't want to traverse into.
if entry.file_type().is_dir() && entry.file_name() == ".git" {
it.skip_current_dir();
continue;
}
if entry.file_type().is_dir() {
// If we are in a directory where we know that we can't use any globs, then we have to
// track each file individually.
if forced_static_directories.contains(&entry.path().to_path_buf()) {
forced_static_directories.push(entry.path().to_path_buf());
root_directories.insert(entry.path().to_path_buf());
continue;
}
// If we are in a directory where the parent is a forced static directory, then this
// will become a forced static directory as well.
if forced_static_directories.contains(&entry.path().parent().unwrap().to_path_buf()) {
forced_static_directories.push(entry.path().to_path_buf());
root_directories.insert(entry.path().to_path_buf());
continue;
}
// If we are in a directory, and the directory is git ignored, then we don't have to
// descent into the directory. However, we have to make sure that we mark the _parent_
// directory as a shallow glob directory because using deep globs from any of the
// parent directories will include this ignored directory which should not be the case.
//
// Another important part is that if one of the ignored directories is a deep glob
// directory, then all of its parents (until the root) should be marked as shallow glob
// directories as well.
if !allowed_paths.contains(&entry.path().to_path_buf()) {
let mut parent = entry.path().parent();
while let Some(parent_path) = parent {
// If the parent is already marked as a valid deep glob directory, then we have
// to mark it as a shallow glob directory instead, because we won't be able to
// use deep globs for this directory anymore.
if deep_globable_directories.contains(parent_path) {
deep_globable_directories.remove(parent_path);
shallow_globable_directories.insert(parent_path.to_path_buf());
}
// If we reached the root, then we can stop.
if parent_path == root {
break;
}
// Mark the parent directory as a shallow glob directory and continue with its
// parent.
shallow_globable_directories.insert(parent_path.to_path_buf());
parent = parent_path.parent();
}
it.skip_current_dir();
continue;
}
// If we are in a directory that is not git ignored, then we can mark this directory as
// a valid deep glob directory. This is only necessary if any of its parents aren't
// marked as deep glob directories already.
else {
let mut found_deep_glob_parent = false;
let mut parent = entry.path().parent();
while let Some(parent_path) = parent {
// If we reached the root, then we can stop.
if parent_path == root {
break;
}
// If the parent is already marked as a deep glob directory, then we can stop
// because this glob will match the current directory already.
if deep_globable_directories.contains(parent_path) {
found_deep_glob_parent = true;
break;
}
parent = parent_path.parent();
}
// If we didn't find a deep glob directory parent, then we can mark this directory
// as a deep glob directory (unless it is the root).
if !found_deep_glob_parent && entry.path() != root {
deep_globable_directories.insert(entry.path().to_path_buf());
}
}
}
// Handle allowed content paths
if is_allowed_content_path(entry.path())
&& allowed_paths.contains(&entry.path().to_path_buf())
{
let path = entry.path();
// Collect the extension for future use when building globs.
if let Some(extension) = path.extension().and_then(|x| x.to_str()) {
found_extensions.insert(extension.to_string());
}
// If the current path is a file inside any of the root directories, then we have to
// track it directly as a static file.
if let Some(parent_path) = path.parent() {
if root_directories.contains(parent_path) {
static_files.push(format!("{}", path.display()));
}
}
}
}
// Build the globs for all globable directories.
let shallow_globs = shallow_globable_directories
.iter()
.map(|path| format!("{}", path.display()));
let deep_globs = deep_globable_directories
.iter()
.map(|path| format!("{}/**", path.display()));
let globs = shallow_globs
.chain(deep_globs)
.flat_map(|path| match found_extensions.len() {
0 => None, // This should never happen
1 => Some(format!(
"{}/*.{}",
path,
found_extensions.iter().next().unwrap()
)),
_ => Some(format!(
"{}/*.{{{}}}",
path,
found_extensions
.iter()
.map(|x| x.to_string())
.collect::<Vec<_>>()
.join(","),
)),
})
.collect::<Vec<_>>();
static_files.extend(globs);
static_files
}
pub fn is_git_ignored_content_path(base: &Path, path: &Path) -> bool {
!WalkBuilder::new(base)
.hidden(false)
.filter_entry(|entry| match entry.file_type() {
Some(file_type) if file_type.is_dir() => entry
.file_name()
.to_str()
.map(|s| s != ".git")
.unwrap_or(false),
_ => true,
})
.build()
.filter_map(Result::ok)
.any(|e| e.path() == path)
}
pub fn is_allowed_content_path(path: &Path) -> bool {
let binary_extensions = include_str!("fixtures/binary-extensions.txt")
.trim()
.lines()
.collect::<Vec<_>>();
let ignored_extensions = include_str!("fixtures/ignored-extensions.txt")
.trim()
.lines()
.collect::<Vec<_>>();
let ignored_files = include_str!("fixtures/ignored-files.txt")
.trim()
.lines()
.collect::<Vec<_>>();
let path = PathBuf::from(path);
// Skip known ignored files
if path
.file_name()
.unwrap()
.to_str()
.map(|s| ignored_files.contains(&s))
.unwrap_or(false)
{
return false;
}
// Skip known ignored extensions
return path
.extension()
.map(|s| s.to_str().unwrap_or_default())
.map(|ext| !ignored_extensions.contains(&ext) && !binary_extensions.contains(&ext))
.unwrap_or(false);
}
#[derive(Debug)]
pub enum IO {
Sequential = 0b0001,
Parallel = 0b0010,
}
impl From<u8> for IO {
fn from(item: u8) -> Self {
match item & 0b0011 {
0b0001 => IO::Sequential,
0b0010 => IO::Parallel,
_ => unimplemented!("Unknown 'IO' strategy"),
}
}
}
#[derive(Debug)]
pub enum Parsing {
Sequential = 0b0100,
Parallel = 0b1000,
}
impl From<u8> for Parsing {
fn from(item: u8) -> Self {
match item & 0b1100 {
0b0100 => Parsing::Sequential,
0b1000 => Parsing::Parallel,
_ => unimplemented!("Unknown 'Parsing' strategy"),
}
}
}
pub fn parse_candidate_strings(input: Vec<ChangedContent>, options: u8) -> Vec<String> {
init_tracing();
match (IO::from(options), Parsing::from(options)) {
(IO::Sequential, Parsing::Sequential) => parse_all_blobs_sync(read_all_files_sync(input)),
(IO::Sequential, Parsing::Parallel) => parse_all_blobs(read_all_files_sync(input)),
(IO::Parallel, Parsing::Sequential) => parse_all_blobs_sync(read_all_files(input)),
(IO::Parallel, Parsing::Parallel) => parse_all_blobs(read_all_files(input)),
}
} }
#[tracing::instrument(skip(changed_content))] #[tracing::instrument(skip(changed_content))]
@ -41,6 +358,30 @@ fn read_all_files(changed_content: Vec<ChangedContent>) -> Vec<Vec<u8>> {
changed_content changed_content
.into_par_iter() .into_par_iter()
.map(|c| match (c.file, c.content) {
(Some(file), None) => match std::fs::read(file) {
Ok(content) => content,
Err(e) => {
event!(tracing::Level::ERROR, "Failed to read file: {:?}", e);
Default::default()
}
},
(None, Some(content)) => content.into_bytes(),
_ => Default::default(),
})
.collect()
}
#[tracing::instrument(skip(changed_content))]
fn read_all_files_sync(changed_content: Vec<ChangedContent>) -> Vec<Vec<u8>> {
event!(
tracing::Level::INFO,
"Reading {:?} file(s)",
changed_content.len()
);
changed_content
.into_iter()
.map(|c| match (c.file, c.content) { .map(|c| match (c.file, c.content) {
(Some(file), None) => std::fs::read(file).unwrap(), (Some(file), None) => std::fs::read(file).unwrap(),
(None, Some(content)) => content.into_bytes(), (None, Some(content)) => content.into_bytes(),
@ -72,3 +413,27 @@ fn parse_all_blobs(blobs: Vec<Vec<u8>>) -> Vec<String> {
result.sort(); result.sort();
result result
} }
#[tracing::instrument(skip(blobs))]
fn parse_all_blobs_sync(blobs: Vec<Vec<u8>>) -> Vec<String> {
let input: Vec<_> = blobs.iter().map(|blob| &blob[..]).collect();
let input = &input[..];
let mut result: Vec<String> = input
.iter()
.map(|input| Extractor::unique(input, Default::default()))
.fold(FxHashSet::default(), |mut a, b| {
a.extend(b);
a
})
.into_iter()
.map(|s| {
// SAFETY: When we parsed the candidates, we already guaranteed that the byte slices
// are valid, therefore we don't have to re-check here when we want to convert it back
// to a string.
unsafe { String::from_utf8_unchecked(s.to_vec()) }
})
.collect();
result.sort();
result
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,248 @@
#[cfg(test)]
mod auto_content {
use std::fs;
use std::process::Command;
use tailwindcss_core::*;
use tempfile::tempdir;
fn test(paths_with_content: &[(&str, Option<&str>)]) -> Vec<String> {
// Create a temporary working directory
let dir = tempdir().unwrap().into_path();
// Initialize this directory as a git repository
let _ = Command::new("git").arg("init").current_dir(&dir).output();
// Create the necessary files
for (path, contents) in paths_with_content {
let path = dir.join(path);
let parent = path.parent().unwrap();
if !parent.exists() {
fs::create_dir_all(parent).unwrap();
}
match contents {
Some(contents) => fs::write(path, contents).unwrap(),
None => fs::write(path, "").unwrap(),
}
}
let base = format!("{}", dir.display());
// Resolve all content paths for the (temporary) current working directory
let mut paths: Vec<_> = resolve_content_paths(ContentPathInfo { base: base.clone() })
.into_iter()
.map(|x| x.replace(&format!("{}/", &base), ""))
.collect();
// Sort the output for easier comparison (depending on internal datastructure the order
// _could_ be random)
paths.sort();
paths
}
#[test]
fn it_should_work_with_a_set_of_root_files() {
let globs = test(&[
("index.html", None),
("a.html", None),
("b.html", None),
("c.html", None),
]);
assert_eq!(globs, vec!["a.html", "b.html", "c.html", "index.html"]);
}
#[test]
fn it_should_work_with_a_set_of_root_files_and_ignore_ignored_files() {
let globs = test(&[
(".gitignore", Some("b.html")),
("index.html", None),
("a.html", None),
("b.html", None),
("c.html", None),
]);
assert_eq!(globs, vec!["a.html", "c.html", "index.html"]);
}
#[test]
fn it_should_list_all_files_in_the_public_folder_explicitly() {
let globs = test(&[
("index.html", None),
("public/a.html", None),
("public/b.html", None),
("public/c.html", None),
]);
assert_eq!(
globs,
vec![
"index.html",
"public/a.html",
"public/b.html",
"public/c.html",
]
);
}
#[test]
fn it_should_list_nested_folders_explicitly_in_the_public_folder() {
let globs = test(&[
("index.html", None),
("public/a.html", None),
("public/b.html", None),
("public/c.html", None),
("public/nested/a.html", None),
("public/nested/b.html", None),
("public/nested/c.html", None),
("public/nested/again/a.html", None),
("public/very/deeply/nested/a.html", None),
]);
assert_eq!(
globs,
vec![
"index.html",
"public/a.html",
"public/b.html",
"public/c.html",
"public/nested/a.html",
"public/nested/again/a.html",
"public/nested/b.html",
"public/nested/c.html",
"public/very/deeply/nested/a.html",
]
);
}
#[test]
fn it_should_list_all_files_in_the_public_folder_explicitly_except_ignored_files() {
let globs = test(&[
(".gitignore", Some("public/b.html\na.html")),
("index.html", None),
("public/a.html", None),
("public/b.html", None),
("public/c.html", None),
]);
assert_eq!(globs, vec!["index.html", "public/c.html",]);
}
#[test]
fn it_should_use_a_glob_for_top_level_folders() {
let globs = test(&[
("index.html", None),
("src/a.html", None),
("src/b.html", None),
("src/c.html", None),
]);
assert_eq!(globs, vec!["index.html", "src/**/*.{py,tpl,js,vue,php,mjs,cts,jsx,tsx,rhtml,slim,handlebars,twig,rs,njk,svelte,liquid,pug,md,ts,heex,mts,astro,nunjucks,rb,eex,haml,cjs,html,hbs,jade,aspx,razor,erb,mustache,mdx}"]);
}
#[test]
fn it_should_ignore_binary_files() {
let globs = test(&[
("index.html", None),
("a.mp4", None),
("b.png", None),
("c.lock", None),
]);
assert_eq!(globs, vec!["index.html"]);
}
#[test]
fn it_should_ignore_known_extensions() {
let globs = test(&[
("index.html", None),
("a.css", None),
("b.sass", None),
("c.less", None),
]);
assert_eq!(globs, vec!["index.html"]);
}
#[test]
fn it_should_ignore_known_files() {
let globs = test(&[
("index.html", None),
("package-lock.json", None),
("yarn.lock", None),
]);
assert_eq!(globs, vec!["index.html"]);
}
#[test]
fn it_should_ignore_and_expand_nested_ignored_folders() {
let globs = test(&[
// Explicitly listed root files
("foo.html", None),
("bar.html", None),
("baz.html", None),
// Nested folder A, using glob
("nested-a/foo.html", None),
("nested-a/bar.html", None),
("nested-a/baz.html", None),
// Nested folder B, with deeply nested files, using glob
("nested-b/deeply-nested/foo.html", None),
("nested-b/deeply-nested/bar.html", None),
("nested-b/deeply-nested/baz.html", None),
// Nested folder C, with ignored sub-folder
("nested-c/foo.html", None),
("nested-c/bar.html", None),
("nested-c/baz.html", None),
// Ignored folder
("nested-c/.gitignore", Some("ignored-folder/")),
("nested-c/ignored-folder/foo.html", None),
("nested-c/ignored-folder/bar.html", None),
("nested-c/ignored-folder/baz.html", None),
// Deeply nested, without issues
("nested-c/sibling-folder/foo.html", None),
("nested-c/sibling-folder/bar.html", None),
("nested-c/sibling-folder/baz.html", None),
// Nested folder D, with deeply nested ignored folder
("nested-d/foo.html", None),
("nested-d/bar.html", None),
("nested-d/baz.html", None),
("nested-d/.gitignore", Some("deep/")),
("nested-d/very/deeply/nested/deep/foo.html", None),
("nested-d/very/deeply/nested/deep/bar.html", None),
("nested-d/very/deeply/nested/deep/baz.html", None),
("nested-d/very/deeply/nested/foo.html", None),
("nested-d/very/deeply/nested/bar.html", None),
("nested-d/very/deeply/nested/baz.html", None),
("nested-d/very/deeply/nested/directory/foo.html", None),
("nested-d/very/deeply/nested/directory/bar.html", None),
("nested-d/very/deeply/nested/directory/baz.html", None),
("nested-d/very/deeply/nested/directory/again/foo.html", None),
]);
assert_eq!(
globs,
vec![
// Listed explicitly because they are in the root
"bar.html",
"baz.html",
"foo.html",
// Listed using a deep glob, because nothing inside is ignored
"nested-a/**/*.{py,tpl,js,vue,php,mjs,cts,jsx,tsx,rhtml,slim,handlebars,twig,rs,njk,svelte,liquid,pug,md,ts,heex,mts,astro,nunjucks,rb,eex,haml,cjs,html,hbs,jade,aspx,razor,erb,mustache,mdx}",
// Listed using a deep glob, because nothing inside is ignored (but contains deeply
// nested folders)
"nested-b/**/*.{py,tpl,js,vue,php,mjs,cts,jsx,tsx,rhtml,slim,handlebars,twig,rs,njk,svelte,liquid,pug,md,ts,heex,mts,astro,nunjucks,rb,eex,haml,cjs,html,hbs,jade,aspx,razor,erb,mustache,mdx}",
// Listed using a shallow glob, because `nested-c` contains ignored folders, deeply
// nested globs can't be used anymore
"nested-c/*.{py,tpl,js,vue,php,mjs,cts,jsx,tsx,rhtml,slim,handlebars,twig,rs,njk,svelte,liquid,pug,md,ts,heex,mts,astro,nunjucks,rb,eex,haml,cjs,html,hbs,jade,aspx,razor,erb,mustache,mdx}",
// Deeply nested folder can use a deep glob again
"nested-c/sibling-folder/**/*.{py,tpl,js,vue,php,mjs,cts,jsx,tsx,rhtml,slim,handlebars,twig,rs,njk,svelte,liquid,pug,md,ts,heex,mts,astro,nunjucks,rb,eex,haml,cjs,html,hbs,jade,aspx,razor,erb,mustache,mdx}",
// The deeply nested folder can use a deep glob, however all of its parents can
// only contain a shallow glob because of the ignored folder
"nested-d/*.{py,tpl,js,vue,php,mjs,cts,jsx,tsx,rhtml,slim,handlebars,twig,rs,njk,svelte,liquid,pug,md,ts,heex,mts,astro,nunjucks,rb,eex,haml,cjs,html,hbs,jade,aspx,razor,erb,mustache,mdx}",
"nested-d/very/*.{py,tpl,js,vue,php,mjs,cts,jsx,tsx,rhtml,slim,handlebars,twig,rs,njk,svelte,liquid,pug,md,ts,heex,mts,astro,nunjucks,rb,eex,haml,cjs,html,hbs,jade,aspx,razor,erb,mustache,mdx}",
"nested-d/very/deeply/*.{py,tpl,js,vue,php,mjs,cts,jsx,tsx,rhtml,slim,handlebars,twig,rs,njk,svelte,liquid,pug,md,ts,heex,mts,astro,nunjucks,rb,eex,haml,cjs,html,hbs,jade,aspx,razor,erb,mustache,mdx}",
"nested-d/very/deeply/nested/*.{py,tpl,js,vue,php,mjs,cts,jsx,tsx,rhtml,slim,handlebars,twig,rs,njk,svelte,liquid,pug,md,ts,heex,mts,astro,nunjucks,rb,eex,haml,cjs,html,hbs,jade,aspx,razor,erb,mustache,mdx}",
"nested-d/very/deeply/nested/directory/**/*.{py,tpl,js,vue,php,mjs,cts,jsx,tsx,rhtml,slim,handlebars,twig,rs,njk,svelte,liquid,pug,md,ts,heex,mts,astro,nunjucks,rb,eex,haml,cjs,html,hbs,jade,aspx,razor,erb,mustache,mdx}",
]
);
}
}

View file

@ -8,8 +8,8 @@ crate-type = ["cdylib"]
[dependencies] [dependencies]
# Default enable napi4 feature, see https://nodejs.org/api/n-api.html#node-api-version-matrix # Default enable napi4 feature, see https://nodejs.org/api/n-api.html#node-api-version-matrix
napi = { version = "2.10.0", default-features = false, features = ["napi4"] } napi = { version = "2.13.1", default-features = false, features = ["napi4"] }
napi-derive = "2.11.1" napi-derive = "2.13.0"
tailwindcss-core = { path = "../core" } tailwindcss-core = { path = "../core" }
rayon = "1.5.3" rayon = "1.5.3"

View file

@ -19,7 +19,7 @@
}, },
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@napi-rs/cli": "^2.15.2" "@napi-rs/cli": "^2.16.5"
}, },
"engines": { "engines": {
"node": ">= 10" "node": ">= 10"

View file

@ -1,3 +1,4 @@
use napi::bindgen_prelude::{FromNapiValue, ToNapiValue};
use std::path::PathBuf; use std::path::PathBuf;
#[macro_use] #[macro_use]
@ -11,16 +12,42 @@ pub struct ChangedContent {
pub extension: String, pub extension: String,
} }
#[napi] impl From<ChangedContent> for tailwindcss_core::ChangedContent {
pub fn parse_candidate_strings_from_files(changed_content: Vec<ChangedContent>) -> Vec<String> { fn from(changed_content: ChangedContent) -> Self {
tailwindcss_core::parse_candidate_strings_from_files( tailwindcss_core::ChangedContent {
changed_content file: changed_content.file.map(PathBuf::from),
.into_iter() content: changed_content.content,
.map(|changed_content| tailwindcss_core::ChangedContent { extension: changed_content.extension,
file: changed_content.file.map(PathBuf::from), }
content: changed_content.content, }
extension: changed_content.extension, }
})
.collect(), #[derive(Debug, Clone)]
) #[napi(object)]
pub struct ContentPathInfo {
pub base: String,
}
#[napi]
pub fn resolve_content_paths(args: ContentPathInfo) -> Vec<String> {
tailwindcss_core::resolve_content_paths(tailwindcss_core::ContentPathInfo { base: args.base })
}
#[derive(Debug)]
#[napi]
pub enum IO {
Sequential = 0b0001,
Parallel = 0b0010,
}
#[derive(Debug)]
#[napi]
pub enum Parsing {
Sequential = 0b0100,
Parallel = 0b1000,
}
#[napi]
pub fn parse_candidate_strings(input: Vec<ChangedContent>, strategy: u8) -> Vec<String> {
tailwindcss_core::parse_candidate_strings(input.into_iter().map(Into::into).collect(), strategy)
} }

12850
package-lock.json generated

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -16,12 +16,9 @@
"integrations/*", "integrations/*",
"oxide/crates/node" "oxide/crates/node"
], ],
"tailwindcss": {
"engine": "oxide"
},
"scripts": { "scripts": {
"dev": "concurrently -n tailwind,oxide-node -c green,yellow 'npm run dev:js' 'npm run dev:rust'", "dev": "concurrently -n tailwind,oxide-node -c green,yellow 'npm run dev:js' 'npm run dev:rust'",
"build": "npm run build:rust && npm run build:js && npm run build:peers", "build": "npm run build:rust && npm run build:js",
"test": "jest", "test": "jest",
"test:integrations": "npm run test -w ./integrations", "test:integrations": "npm run test -w ./integrations",
"style": "eslint .", "style": "eslint .",
@ -29,8 +26,7 @@
"dev:rust": "npm run --prefix ./oxide dev:node", "dev:rust": "npm run --prefix ./oxide dev:node",
"dev:js": "npm run build:js -- --watch", "dev:js": "npm run build:js -- --watch",
"build:rust": "npm run --prefix ./oxide build:node", "build:rust": "npm run --prefix ./oxide build:node",
"build:js": "npm run generate:plugin-list && swc src --out-dir lib --copy-files --delete-dir-on-start --config jsc.transform.optimizer.globals.vars.__OXIDE__='\"true\"'", "build:js": "npm run generate:plugin-list && swc src --out-dir lib --copy-files --delete-dir-on-start",
"build:peers": "esbuild lib/cli-peer-dependencies.js --bundle --platform=node --outfile=peers/index.js --define:process.env.CSS_TRANSFORMER_WASM=false",
"generate:plugin-list": "node -r @swc/register scripts/create-plugin-list.js", "generate:plugin-list": "node -r @swc/register scripts/create-plugin-list.js",
"generate:types": "node -r @swc/register scripts/generate-types.js" "generate:types": "node -r @swc/register scripts/generate-types.js"
}, },
@ -38,7 +34,6 @@
"src/*", "src/*",
"cli/*", "cli/*",
"lib/*", "lib/*",
"peers/*",
"scripts/*.js", "scripts/*.js",
"stubs/*", "stubs/*",
"nesting/*", "nesting/*",
@ -48,57 +43,53 @@
"*.js" "*.js"
], ],
"devDependencies": { "devDependencies": {
"@swc/cli": "^0.1.62", "@swc/cli": "^0.1.63",
"@swc/core": "^1.3.55", "@swc/core": "^1.3.95",
"@swc/jest": "^0.2.26", "@swc/jest": "^0.2.29",
"@swc/register": "^0.1.10", "@swc/register": "^0.1.10",
"autoprefixer": "^10.4.14",
"concurrently": "^8.0.1", "concurrently": "^8.0.1",
"cssnano": "^6.0.0", "eslint": "^8.52.0",
"esbuild": "^0.17.18", "eslint-config-prettier": "^9.0.0",
"eslint": "^8.39.0",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^4.2.1",
"jest": "^29.5.0", "jest": "^29.7.0",
"jest-diff": "^29.5.0", "jest-diff": "^29.7.0",
"prettier": "^2.8.8", "prettier": "^2.8.8",
"rimraf": "^5.0.0", "rimraf": "^5.0.0",
"source-map-js": "^1.0.2", "source-map-js": "^1.0.2",
"turbo": "^1.9.3" "turbo": "^1.10.16"
}, },
"dependencies": { "dependencies": {
"@alloc/quick-lru": "^5.2.0", "@alloc/quick-lru": "^5.2.0",
"@tailwindcss/oxide": "file:oxide/crates/node", "@tailwindcss/oxide": "file:oxide/crates/node",
"arg": "^5.0.2", "arg": "^5.0.2",
"browserslist": "^4.21.5", "browserslist": "^4.22.1",
"chokidar": "^3.5.3", "chokidar": "^3.5.3",
"didyoumean": "^1.2.2", "didyoumean": "^1.2.2",
"dlv": "^1.1.3", "dlv": "^1.1.3",
"fast-glob": "^3.2.12", "fast-glob": "^3.3.2",
"glob-parent": "^6.0.2", "glob-parent": "^6.0.2",
"is-glob": "^4.0.3", "is-glob": "^4.0.3",
"jiti": "^1.18.2", "jiti": "^1.21.0",
"lightningcss": "^1.18.0", "lightningcss": "^1.22.1",
"lilconfig": "^2.1.0", "lilconfig": "^3.0.0",
"micromatch": "^4.0.5", "micromatch": "^4.0.5",
"normalize-path": "^3.0.0", "normalize-path": "^3.0.0",
"object-hash": "^3.0.0", "object-hash": "^3.0.0",
"picocolors": "^1.0.0", "picocolors": "^1.0.0",
"postcss": "^8.4.23", "postcss": "^8.4.31",
"postcss-import": "^15.1.0", "postcss-import": "^15.1.0",
"postcss-js": "^4.0.1", "postcss-js": "^4.0.1",
"postcss-load-config": "^4.0.1", "postcss-load-config": "^4.0.2",
"postcss-nested": "^6.0.1", "postcss-nested": "^6.0.1",
"postcss-selector-parser": "^6.0.11", "postcss-selector-parser": "^6.0.12",
"postcss-value-parser": "^4.2.0", "postcss-value-parser": "^4.2.0",
"resolve": "^1.22.2", "resolve": "^1.22.8",
"sucrase": "^3.32.0" "sucrase": "^3.34.0"
}, },
"browserslist": [ "browserslist": [
"> 1%", "defaults and supports css-variables and supports css-matches-pseudo",
"not edge <= 18", "not android <= 117",
"not ie 11", "safari >= 14"
"not op_mini all"
], ],
"jest": { "jest": {
"testTimeout": 30000, "testTimeout": 30000,

View file

@ -1,122 +0,0 @@
{
"name": "tailwindcss",
"version": "3.3.2",
"description": "A utility-first CSS framework for rapidly building custom user interfaces.",
"license": "MIT",
"main": "lib/index.js",
"types": "types/index.d.ts",
"repository": "https://github.com/tailwindlabs/tailwindcss.git",
"bugs": "https://github.com/tailwindlabs/tailwindcss/issues",
"homepage": "https://tailwindcss.com",
"bin": {
"tailwind": "lib/cli.js",
"tailwindcss": "lib/cli.js"
},
"tailwindcss": {
"engine": "stable"
},
"scripts": {
"prebuild": "npm run generate && rimraf lib",
"build": "swc src --out-dir lib --copy-files --config jsc.transform.optimizer.globals.vars.__OXIDE__='\"false\"'",
"postbuild": "esbuild lib/cli-peer-dependencies.js --bundle --platform=node --outfile=peers/index.js --define:process.env.CSS_TRANSFORMER_WASM=false",
"rebuild-fixtures": "npm run build && node -r @swc/register scripts/rebuildFixtures.js",
"style": "eslint .",
"pretest": "npm run generate",
"test": "jest",
"test:integrations": "npm run test --prefix ./integrations",
"install:integrations": "node scripts/install-integrations.js",
"generate:plugin-list": "node -r @swc/register scripts/create-plugin-list.js",
"generate:types": "node -r @swc/register scripts/generate-types.js",
"generate": "npm run generate:plugin-list && npm run generate:types",
"release-channel": "node ./scripts/release-channel.js",
"release-notes": "node ./scripts/release-notes.js",
"prepublishOnly": "npm install --force && npm run build"
},
"files": [
"src/*",
"cli/*",
"lib/*",
"peers/*",
"scripts/*.js",
"stubs/*",
"nesting/*",
"types/**/*",
"*.d.ts",
"*.css",
"*.js"
],
"devDependencies": {
"@swc/cli": "^0.1.62",
"@swc/core": "^1.3.55",
"@swc/jest": "^0.2.26",
"@swc/register": "^0.1.10",
"autoprefixer": "^10.4.14",
"browserslist": "^4.21.5",
"concurrently": "^8.0.1",
"cssnano": "^6.0.0",
"esbuild": "^0.17.18",
"eslint": "^8.39.0",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-prettier": "^4.2.1",
"jest": "^29.5.0",
"jest-diff": "^29.5.0",
"lightningcss": "1.18.0",
"prettier": "^2.8.8",
"rimraf": "^5.0.0",
"source-map-js": "^1.0.2",
"turbo": "^1.9.3"
},
"dependencies": {
"@alloc/quick-lru": "^5.2.0",
"arg": "^5.0.2",
"chokidar": "^3.5.3",
"didyoumean": "^1.2.2",
"dlv": "^1.1.3",
"fast-glob": "^3.2.12",
"glob-parent": "^6.0.2",
"is-glob": "^4.0.3",
"jiti": "^1.18.2",
"lilconfig": "^2.1.0",
"micromatch": "^4.0.5",
"normalize-path": "^3.0.0",
"object-hash": "^3.0.0",
"picocolors": "^1.0.0",
"postcss": "^8.4.23",
"postcss-import": "^15.1.0",
"postcss-js": "^4.0.1",
"postcss-load-config": "^4.0.1",
"postcss-nested": "^6.0.1",
"postcss-selector-parser": "^6.0.11",
"postcss-value-parser": "^4.2.0",
"resolve": "^1.22.2",
"sucrase": "^3.32.0"
},
"browserslist": [
"> 1%",
"not edge <= 18",
"not ie 11",
"not op_mini all"
],
"jest": {
"testTimeout": 30000,
"setupFilesAfterEnv": [
"<rootDir>/jest/customMatchers.js"
],
"testPathIgnorePatterns": [
"/node_modules/",
"/integrations/",
"/standalone-cli/",
"\\.test\\.skip\\.js$"
],
"transformIgnorePatterns": [
"node_modules/(?!lightningcss)"
],
"transform": {
"\\.js$": "@swc/jest",
"\\.ts$": "@swc/jest"
}
},
"engines": {
"node": ">=14.0.0"
}
}

25
resolveConfig.d.ts vendored
View file

@ -1,11 +1,30 @@
import type { Config, ResolvableTo } from './types/config' import { Config, ResolvableTo, ThemeConfig } from './types/config'
import { DefaultTheme } from './types/generated/default-theme'
import { DefaultColors } from './types/generated/colors'
type ResolvedConfig<T extends Config> = Omit<T, 'theme'> & {
theme: MergeThemes<
UnwrapResolvables<Omit<T['theme'], 'extend'>>,
T['theme'] extends { extend: infer TExtend } ? UnwrapResolvables<TExtend> : {}
>
}
type UnwrapResolvables<T> = { type UnwrapResolvables<T> = {
[K in keyof T]: T[K] extends ResolvableTo<infer R> ? R : T[K] [K in keyof T]: T[K] extends ResolvableTo<infer R> ? R : T[K]
} }
type ResolvedConfig<T extends Config> = Omit<T, 'theme'> & { type ThemeConfigResolved = UnwrapResolvables<ThemeConfig>
theme: UnwrapResolvables<T['theme']> type DefaultThemeFull = DefaultTheme & { colors: DefaultColors }
type MergeThemes<Overrides extends object, Extensions extends object> = {
[K in keyof ThemeConfigResolved | keyof Overrides]: (K extends keyof Overrides
? Overrides[K]
: K extends keyof DefaultThemeFull
? DefaultThemeFull[K]
: K extends keyof ThemeConfigResolved
? ThemeConfigResolved[K]
: never) &
(K extends keyof Extensions ? Extensions[K] : {})
} }
declare function resolveConfig<T extends Config>(config: T): ResolvedConfig<T> declare function resolveConfig<T extends Config>(config: T): ResolvedConfig<T>

View file

@ -91,9 +91,8 @@ fs.writeFileSync(
path.join(process.cwd(), 'types', 'generated', 'default-theme.d.ts'), path.join(process.cwd(), 'types', 'generated', 'default-theme.d.ts'),
prettier.format( prettier.format(
` `
import { Config } from '../../types'
type CSSDeclarationList = Record<string, string> type CSSDeclarationList = Record<string, string>
export type DefaultTheme = Config['theme'] & { ${defaultThemeTypes} } export type DefaultTheme = { ${defaultThemeTypes} }
`, `,
{ {
semi: false, semi: false,

View file

@ -1,5 +1,5 @@
// Given a version, figure out what the release notes are so that we can use this to pre-fill the // Given a version, figure out what the release notes are so that we can use this to pre-fill the
// relase notes on a GitHub release for the current version. // release notes on a GitHub release for the current version.
let path = require('path') let path = require('path')
let fs = require('fs') let fs = require('fs')

View file

@ -1,40 +0,0 @@
let fs = require('fs')
let path = require('path')
let engines = {
stable: {
files: [
path.resolve(__dirname, '..', 'package.stable.json'),
path.resolve(__dirname, '..', 'package-lock.stable.json'),
],
},
oxide: {
files: [
path.resolve(__dirname, '..', 'package.oxide.json'),
path.resolve(__dirname, '..', 'package-lock.oxide.json'),
],
},
}
// Find out what the current engine is that we are using:
let [otherEngine, info] = Object.entries(engines).find(([, info]) =>
info.files.every((file) => fs.existsSync(file))
)
let currentEngine = otherEngine === 'oxide' ? 'stable' : 'oxide'
console.log(`Current engine: \`${currentEngine}\`, swapping to \`${otherEngine}\``)
// Swap the engines
for (let file of info.files) {
fs.renameSync(
file.replace(`.${otherEngine}`, ''),
file.replace(`.${otherEngine}`, `.${currentEngine}`)
)
}
for (let file of engines[otherEngine].files) {
fs.renameSync(file, file.replace(`.${otherEngine}`, ''))
}
console.log(
'Engines have been swapped. Make sure to run `npm install` to update your dependencies.'
)

View file

@ -1,15 +0,0 @@
export function lazyPostcss() {
return require('postcss')
}
export function lazyPostcssImport() {
return require('postcss-import')
}
export function lazyAutoprefixer() {
return require('autoprefixer')
}
export function lazyCssnano() {
return require('cssnano')
}

View file

@ -1,7 +1,3 @@
#!/usr/bin/env node #!/usr/bin/env node
if (__OXIDE__) { module.exports = require('./cli/index')
module.exports = require('./oxide/cli')
} else {
module.exports = require('./cli/index')
}

View file

@ -1,56 +0,0 @@
// @ts-check
import {
// @ts-ignore
lazyPostcss,
// @ts-ignore
lazyPostcssImport,
// @ts-ignore
lazyCssnano,
// @ts-ignore
lazyAutoprefixer,
} from '../../../peers/index.js'
/**
* @returns {import('postcss')}
*/
export function loadPostcss() {
// Try to load a local `postcss` version first
try {
return require('postcss')
} catch {}
return lazyPostcss()
}
export function loadPostcssImport() {
// Try to load a local `postcss-import` version first
try {
return require('postcss-import')
} catch {}
return lazyPostcssImport()
}
export function loadCssNano() {
let options = { preset: ['default', { cssDeclarationSorter: false }] }
// Try to load a local `cssnano` version first
try {
return require('cssnano')
} catch {}
return lazyCssnano()(options)
}
export function loadAutoprefixer() {
// Try to load a local `autoprefixer` version first
try {
return require('autoprefixer')
} catch {}
return lazyAutoprefixer()
}

View file

@ -25,6 +25,10 @@ export async function build(args) {
process.exit(9) process.exit(9)
} }
if (args['--no-autoprefixer']) {
console.error('[deprecation] The --no-autoprefixer flag is deprecated and has no effect.')
}
// TODO: Reference the @config path here if exists // TODO: Reference the @config path here if exists
let configPath = args['--config'] ? args['--config'] : resolveDefaultConfigPath() let configPath = args['--config'] ? args['--config'] : resolveDefaultConfigPath()

View file

@ -1,14 +1,17 @@
// @ts-check // @ts-check
import pkg from '../../../package.json'
import path from 'path' import path from 'path'
import fs from 'fs' import fs from 'fs'
import postcss from 'postcss'
import postcssrc from 'postcss-load-config' import postcssrc from 'postcss-load-config'
import browserslist from 'browserslist'
import lightning, { Features } from 'lightningcss'
import { lilconfig } from 'lilconfig' import { lilconfig } from 'lilconfig'
import loadPlugins from 'postcss-load-config/src/plugins' // Little bit scary, looking at private/internal API import loadPlugins from 'postcss-load-config/src/plugins' // Little bit scary, looking at private/internal API
import loadOptions from 'postcss-load-config/src/options' // Little bit scary, looking at private/internal API import loadOptions from 'postcss-load-config/src/options' // Little bit scary, looking at private/internal API
import tailwind from '../../processTailwindFeatures' import tailwind from '../../processTailwindFeatures'
import { loadAutoprefixer, loadCssNano, loadPostcss, loadPostcssImport } from './deps'
import { formatNodes, drainStdin, outputFile } from './utils' import { formatNodes, drainStdin, outputFile } from './utils'
import { env } from '../../lib/sharedState' import { env } from '../../lib/sharedState'
import resolveConfig from '../../../resolveConfig.js' import resolveConfig from '../../../resolveConfig.js'
@ -19,6 +22,50 @@ import { findAtConfigPath } from '../../lib/findAtConfigPath.js'
import log from '../../util/log' import log from '../../util/log'
import { loadConfig } from '../../lib/load-config' import { loadConfig } from '../../lib/load-config'
import getModuleDependencies from '../../lib/getModuleDependencies' import getModuleDependencies from '../../lib/getModuleDependencies'
import { validateConfig } from '../../util/validateConfig'
import { handleImportAtRules } from '../../lib/handleImportAtRules'
import { flagEnabled } from '../../featureFlags'
function license() {
return `/* ! tailwindcss v${pkg.version} | MIT License | https://tailwindcss.com */\n`
}
async function lightningcss(result, { map = true, minify = true } = {}) {
try {
let resolvedBrowsersListConfig = browserslist.findConfig(
result.opts.from ?? process.cwd()
)?.defaults
let defaultBrowsersListConfig = pkg.browserslist
let browsersListConfig = resolvedBrowsersListConfig ?? defaultBrowsersListConfig
let transformed = lightning.transform({
filename: result.opts.from || 'input.css',
code: Buffer.from(result.css, 'utf-8'),
minify,
sourceMap: result.map === undefined ? map : !!result.map,
inputSourceMap: result.map ? result.map.toString() : undefined,
targets: lightning.browserslistToTargets(browserslist(browsersListConfig)),
include: Features.Nesting,
exclude: Features.LogicalProperties,
})
return Object.assign(result, {
css: transformed.code.toString(),
map: result.map
? Object.assign(result.map, {
toString() {
return transformed.map?.toString()
},
})
: result.map,
})
} catch (err) {
console.error('Unable to use Lightning CSS. Using raw version instead.')
console.error(err)
return result
}
}
/** /**
* *
@ -75,39 +122,6 @@ async function loadPostCssPlugins(customPostCssPath) {
return [beforePlugins, afterPlugins, config.options] return [beforePlugins, afterPlugins, config.options]
} }
function loadBuiltinPostcssPlugins() {
let postcss = loadPostcss()
let IMPORT_COMMENT = '__TAILWIND_RESTORE_IMPORT__: '
return [
[
(root) => {
root.walkAtRules('import', (rule) => {
if (rule.params.slice(1).startsWith('tailwindcss/')) {
rule.after(postcss.comment({ text: IMPORT_COMMENT + rule.params }))
rule.remove()
}
})
},
loadPostcssImport(),
(root) => {
root.walkComments((rule) => {
if (rule.text.startsWith(IMPORT_COMMENT)) {
rule.after(
postcss.atRule({
name: 'import',
params: rule.text.replace(IMPORT_COMMENT, ''),
})
)
rule.remove()
}
})
},
],
[],
{},
]
}
let state = { let state = {
/** @type {any} */ /** @type {any} */
context: null, context: null,
@ -118,7 +132,7 @@ let state = {
/** @type {{content: string, extension: string}[]} */ /** @type {{content: string, extension: string}[]} */
changedContent: [], changedContent: [],
/** @type {ReturnType<typeof load> | null} */ /** @type {{config: import('../../../types').Config, dependencies: Set<string>, dispose: Function } | null} */
configBag: null, configBag: null,
contextDependencies: new Set(), contextDependencies: new Set(),
@ -160,8 +174,7 @@ let state = {
}, },
} }
// @ts-ignore this.configBag.config = validateConfig(resolveConfig(this.configBag.config))
this.configBag.config = resolveConfig(this.configBag.config, { content: { files: [] } })
// Override content files if `--content` has been passed explicitly // Override content files if `--content` has been passed explicitly
if (content?.length > 0) { if (content?.length > 0) {
@ -185,7 +198,7 @@ let state = {
let files = fastGlob.sync(this.contentPatterns.all) let files = fastGlob.sync(this.contentPatterns.all)
for (let file of files) { for (let file of files) {
if (__OXIDE__) { if (flagEnabled(this.config, 'oxideParser')) {
content.push({ content.push({
file, file,
extension: path.extname(file).slice(1), extension: path.extname(file).slice(1),
@ -211,16 +224,16 @@ let state = {
}, },
getContext({ createContext, cliConfigPath, root, result, content }) { getContext({ createContext, cliConfigPath, root, result, content }) {
env.DEBUG && console.time('Searching for config')
let configPath = findAtConfigPath(root, result) ?? cliConfigPath
env.DEBUG && console.timeEnd('Searching for config')
if (this.context) { if (this.context) {
this.context.changedContent = this.changedContent.splice(0) this.context.changedContent = this.changedContent.splice(0)
return this.context return this.context
} }
env.DEBUG && console.time('Searching for config')
let configPath = findAtConfigPath(root, result) ?? cliConfigPath
env.DEBUG && console.timeEnd('Searching for config')
env.DEBUG && console.time('Loading config') env.DEBUG && console.time('Loading config')
let config = this.loadConfig(configPath, content) let config = this.loadConfig(configPath, content)
env.DEBUG && console.timeEnd('Loading config') env.DEBUG && console.timeEnd('Loading config')
@ -251,8 +264,6 @@ let state = {
} }
export async function createProcessor(args, cliConfigPath) { export async function createProcessor(args, cliConfigPath) {
let postcss = loadPostcss()
let input = args['--input'] let input = args['--input']
let output = args['--output'] let output = args['--output']
let includePostCss = args['--postcss'] let includePostCss = args['--postcss']
@ -260,7 +271,9 @@ export async function createProcessor(args, cliConfigPath) {
let [beforePlugins, afterPlugins, postcssOptions] = includePostCss let [beforePlugins, afterPlugins, postcssOptions] = includePostCss
? await loadPostCssPlugins(customPostCssPath) ? await loadPostCssPlugins(customPostCssPath)
: loadBuiltinPostcssPlugins() : [[], [], {}]
beforePlugins.unshift(...handleImportAtRules())
if (args['--purge']) { if (args['--purge']) {
log.warn('purge-flag-deprecated', [ log.warn('purge-flag-deprecated', [
@ -278,9 +291,9 @@ export async function createProcessor(args, cliConfigPath) {
let tailwindPlugin = () => { let tailwindPlugin = () => {
return { return {
postcssPlugin: 'tailwindcss', postcssPlugin: 'tailwindcss',
Once(root, { result }) { async Once(root, { result }) {
env.DEBUG && console.time('Compiling CSS') env.DEBUG && console.time('Compiling CSS')
tailwind(({ createContext }) => { await tailwind(({ createContext }) => {
console.error() console.error()
console.error('Rebuilding...') console.error('Rebuilding...')
@ -306,8 +319,6 @@ export async function createProcessor(args, cliConfigPath) {
tailwindPlugin, tailwindPlugin,
!args['--minify'] && formatNodes, !args['--minify'] && formatNodes,
...afterPlugins, ...afterPlugins,
!args['--no-autoprefixer'] && loadAutoprefixer(),
args['--minify'] && loadCssNano(),
].filter(Boolean) ].filter(Boolean)
/** @type {import('postcss').Processor} */ /** @type {import('postcss').Processor} */
@ -331,9 +342,20 @@ export async function createProcessor(args, cliConfigPath) {
async function build() { async function build() {
let start = process.hrtime.bigint() let start = process.hrtime.bigint()
let options = {
...postcssOptions,
from: input,
to: output,
}
return readInput() return readInput()
.then((css) => processor.process(css, { ...postcssOptions, from: input, to: output })) .then((css) => processor.process(css, options))
.then((result) =>
lightningcss(result, {
...options,
minify: !!args['--minify'],
})
)
.then((result) => { .then((result) => {
if (!state.watcher) { if (!state.watcher) {
return result return result
@ -356,12 +378,12 @@ export async function createProcessor(args, cliConfigPath) {
}) })
.then((result) => { .then((result) => {
if (!output) { if (!output) {
process.stdout.write(result.css) process.stdout.write(license() + result.css)
return return
} }
return Promise.all([ return Promise.all([
outputFile(result.opts.to, result.css), outputFile(result.opts.to, license() + result.css),
result.map && outputFile(result.opts.to + '.map', result.map.toString()), result.map && outputFile(result.opts.to + '.map', result.map.toString()),
]) ])
}) })

View file

@ -164,7 +164,7 @@ export function createWatcher(args, { state, rebuild }) {
// This is very likely a chokidar bug but it's one we need to work around // This is very likely a chokidar bug but it's one we need to work around
// We treat this as a change event and rebuild the CSS // We treat this as a change event and rebuild the CSS
watcher.on('raw', (evt, filePath, meta) => { watcher.on('raw', (evt, filePath, meta) => {
if (evt !== 'rename') { if (evt !== 'rename' || filePath === null) {
return return
} }

View file

@ -70,6 +70,7 @@ let commands = {
description: 'Path to a custom config file', description: 'Path to a custom config file',
}, },
'--no-autoprefixer': { '--no-autoprefixer': {
deprecated: true,
type: Boolean, type: Boolean,
description: 'Disable autoprefixer', description: 'Disable autoprefixer',
}, },

View file

@ -10,7 +10,6 @@ import withAlphaVariable, { withAlphaValue } from './util/withAlphaVariable'
import toColorValue from './util/toColorValue' import toColorValue from './util/toColorValue'
import isPlainObject from './util/isPlainObject' import isPlainObject from './util/isPlainObject'
import transformThemeValue from './util/transformThemeValue' import transformThemeValue from './util/transformThemeValue'
import { version as tailwindVersion } from '../package.json'
import log from './util/log' import log from './util/log'
import { import {
normalizeScreens, normalizeScreens,
@ -22,6 +21,7 @@ import { formatBoxShadowValue, parseBoxShadowValue } from './util/parseBoxShadow
import { removeAlphaVariables } from './util/removeAlphaVariables' import { removeAlphaVariables } from './util/removeAlphaVariables'
import { flagEnabled } from './featureFlags' import { flagEnabled } from './featureFlags'
import { normalize } from './util/dataTypes' import { normalize } from './util/dataTypes'
import { INTERNAL_FEATURES } from './lib/setupContextUtils'
export let variantPlugins = { export let variantPlugins = {
pseudoElementVariants: ({ addVariant }) => { pseudoElementVariants: ({ addVariant }) => {
@ -80,7 +80,7 @@ export let variantPlugins = {
}) })
}, },
pseudoClassVariants: ({ addVariant, matchVariant, config }) => { pseudoClassVariants: ({ addVariant, matchVariant, config, prefix }) => {
let pseudoVariants = [ let pseudoVariants = [
// Positional // Positional
['first', '&:first-child'], ['first', '&:first-child'],
@ -151,12 +151,12 @@ export let variantPlugins = {
let variants = { let variants = {
group: (_, { modifier }) => group: (_, { modifier }) =>
modifier modifier
? [`:merge(.group\\/${escapeClassName(modifier)})`, ' &'] ? [`:merge(${prefix('.group')}\\/${escapeClassName(modifier)})`, ' &']
: [`:merge(.group)`, ' &'], : [`:merge(${prefix('.group')})`, ' &'],
peer: (_, { modifier }) => peer: (_, { modifier }) =>
modifier modifier
? [`:merge(.peer\\/${escapeClassName(modifier)})`, ' ~ &'] ? [`:merge(${prefix('.peer')}\\/${escapeClassName(modifier)})`, ' ~ &']
: [`:merge(.peer)`, ' ~ &'], : [`:merge(${prefix('.peer')})`, ' ~ &'],
} }
for (let [name, fn] of Object.entries(variants)) { for (let [name, fn] of Object.entries(variants)) {
@ -192,7 +192,12 @@ export let variantPlugins = {
return result.slice(0, start) + a + result.slice(start + 1, end) + b + result.slice(end) return result.slice(0, start) + a + result.slice(start + 1, end) + b + result.slice(end)
}, },
{ values: Object.fromEntries(pseudoVariants) } {
values: Object.fromEntries(pseudoVariants),
[INTERNAL_FEATURES]: {
respectPrefix: false,
},
}
) )
} }
}, },
@ -364,7 +369,7 @@ export let variantPlugins = {
let check = normalize(value) let check = normalize(value)
let isRaw = /^\w*\s*\(/.test(check) let isRaw = /^\w*\s*\(/.test(check)
// Chrome has a bug where `(condtion1)or(condition2)` is not valid // Chrome has a bug where `(condition1)or(condition2)` is not valid
// But `(condition1) or (condition2)` is supported. // But `(condition1) or (condition2)` is supported.
check = isRaw ? check.replace(/\b(and|or|not)\b/g, ' $1 ') : check check = isRaw ? check.replace(/\b(and|or|not)\b/g, ' $1 ') : check
@ -386,6 +391,26 @@ export let variantPlugins = {
) )
}, },
hasVariants: ({ matchVariant }) => {
matchVariant('has', (value) => `&:has(${normalize(value)})`, { values: {} })
matchVariant(
'group-has',
(value, { modifier }) =>
modifier
? `:merge(.group\\/${modifier}):has(${normalize(value)}) &`
: `:merge(.group):has(${normalize(value)}) &`,
{ values: {} }
)
matchVariant(
'peer-has',
(value, { modifier }) =>
modifier
? `:merge(.peer\\/${modifier}):has(${normalize(value)}) ~ &`
: `:merge(.peer):has(${normalize(value)}) ~ &`,
{ values: {} }
)
},
ariaVariants: ({ matchVariant, theme }) => { ariaVariants: ({ matchVariant, theme }) => {
matchVariant('aria', (value) => `&[aria-${normalize(value)}]`, { values: theme('aria') ?? {} }) matchVariant('aria', (value) => `&[aria-${normalize(value)}]`, { values: theme('aria') ?? {} })
matchVariant( matchVariant(
@ -435,15 +460,20 @@ export let variantPlugins = {
addVariant('contrast-more', '@media (prefers-contrast: more)') addVariant('contrast-more', '@media (prefers-contrast: more)')
addVariant('contrast-less', '@media (prefers-contrast: less)') addVariant('contrast-less', '@media (prefers-contrast: less)')
}, },
forcedColorsVariants: ({ addVariant }) => {
addVariant('forced-colors', '@media (forced-colors: active)')
},
} }
let cssTransformValue = [ let cssTransformValue = [
'translate(var(--tw-translate-x), var(--tw-translate-y))', 'var(--tw-translate-x)',
'rotate(var(--tw-rotate))', 'var(--tw-translate-y)',
'skewX(var(--tw-skew-x))', 'var(--tw-rotate)',
'skewY(var(--tw-skew-y))', 'var(--tw-skew-x)',
'scaleX(var(--tw-scale-x))', 'var(--tw-skew-y)',
'scaleY(var(--tw-scale-y))', 'var(--tw-scale-x)',
'var(--tw-scale-y)',
].join(' ') ].join(' ')
let cssFilterValue = [ let cssFilterValue = [
@ -476,12 +506,7 @@ export let corePlugins = {
fs.readFileSync(path.join(__dirname, './css/preflight.css'), 'utf8') fs.readFileSync(path.join(__dirname, './css/preflight.css'), 'utf8')
) )
addBase([ addBase(preflightStyles.nodes)
postcss.comment({
text: `! tailwindcss v${tailwindVersion} | MIT License | https://tailwindcss.com`,
}),
...preflightStyles.nodes,
])
}, },
container: (() => { container: (() => {
@ -660,6 +685,8 @@ export let corePlugins = {
float: ({ addUtilities }) => { float: ({ addUtilities }) => {
addUtilities({ addUtilities({
'.float-start': { float: 'inline-start' },
'.float-end': { float: 'inline-end' },
'.float-right': { float: 'right' }, '.float-right': { float: 'right' },
'.float-left': { float: 'left' }, '.float-left': { float: 'left' },
'.float-none': { float: 'none' }, '.float-none': { float: 'none' },
@ -668,6 +695,8 @@ export let corePlugins = {
clear: ({ addUtilities }) => { clear: ({ addUtilities }) => {
addUtilities({ addUtilities({
'.clear-start': { clear: 'inline-start' },
'.clear-end': { clear: 'inline-end' },
'.clear-left': { clear: 'left' }, '.clear-left': { clear: 'left' },
'.clear-right': { clear: 'right' }, '.clear-right': { clear: 'right' },
'.clear-both': { clear: 'both' }, '.clear-both': { clear: 'both' },
@ -753,6 +782,8 @@ export let corePlugins = {
aspectRatio: createUtilityPlugin('aspectRatio', [['aspect', ['aspect-ratio']]]), aspectRatio: createUtilityPlugin('aspectRatio', [['aspect', ['aspect-ratio']]]),
size: createUtilityPlugin('size', [['size', ['width', 'height']]]),
height: createUtilityPlugin('height', [['h', ['height']]]), height: createUtilityPlugin('height', [['h', ['height']]]),
maxHeight: createUtilityPlugin('maxHeight', [['max-h', ['maxHeight']]]), maxHeight: createUtilityPlugin('maxHeight', [['max-h', ['maxHeight']]]),
minHeight: createUtilityPlugin('minHeight', [['min-h', ['minHeight']]]), minHeight: createUtilityPlugin('minHeight', [['min-h', ['minHeight']]]),
@ -829,72 +860,97 @@ export let corePlugins = {
}, },
transformOrigin: createUtilityPlugin('transformOrigin', [['origin', ['transformOrigin']]]), transformOrigin: createUtilityPlugin('transformOrigin', [['origin', ['transformOrigin']]]),
translate: createUtilityPlugin(
'translate', translate: ({ matchUtilities, theme }) => {
[ matchUtilities(
[ {
[ 'translate-x': (value) => {
'translate-x', return {
[['@defaults transform', {}], '--tw-translate-x', ['transform', cssTransformValue]], '@defaults transform': {},
], '--tw-translate-x': `translateX(${value})`,
[ transform: cssTransformValue,
'translate-y', }
[['@defaults transform', {}], '--tw-translate-y', ['transform', cssTransformValue]], },
], 'translate-y': (value) => {
], return {
], '@defaults transform': {},
{ supportsNegativeValues: true } '--tw-translate-y': `translateY(${value})`,
), transform: cssTransformValue,
rotate: createUtilityPlugin( }
'rotate', },
[['rotate', [['@defaults transform', {}], '--tw-rotate', ['transform', cssTransformValue]]]], },
{ supportsNegativeValues: true } { values: theme('translate'), supportsNegativeValues: true }
), )
skew: createUtilityPlugin( },
'skew',
[ rotate: ({ matchUtilities, theme }) => {
[ matchUtilities(
['skew-x', [['@defaults transform', {}], '--tw-skew-x', ['transform', cssTransformValue]]], {
['skew-y', [['@defaults transform', {}], '--tw-skew-y', ['transform', cssTransformValue]]], rotate: (value) => {
], return {
], '@defaults transform': {},
{ supportsNegativeValues: true } '--tw-rotate': `rotate(${value})`,
), transform: cssTransformValue,
scale: createUtilityPlugin( }
'scale', },
[ },
[ { values: theme('rotate'), supportsNegativeValues: true }
'scale', )
[ },
['@defaults transform', {}],
'--tw-scale-x', skew: ({ matchUtilities, theme }) => {
'--tw-scale-y', matchUtilities(
['transform', cssTransformValue], {
], 'skew-x': (value) => {
], return {
[ '@defaults transform': {},
[ '--tw-skew-x': `skewX(${value})`,
'scale-x', transform: cssTransformValue,
[['@defaults transform', {}], '--tw-scale-x', ['transform', cssTransformValue]], }
], },
[ 'skew-y': (value) => {
'scale-y', return {
[['@defaults transform', {}], '--tw-scale-y', ['transform', cssTransformValue]], '@defaults transform': {},
], '--tw-skew-y': `skewY(${value})`,
], transform: cssTransformValue,
], }
{ supportsNegativeValues: true } },
), },
{ values: theme('skew'), supportsNegativeValues: true }
)
},
scale: ({ matchUtilities, theme }) => {
matchUtilities(
{
'scale-x': (value) => {
return {
'@defaults transform': {},
'--tw-scale-x': `scaleX(${value})`,
transform: cssTransformValue,
}
},
'scale-y': (value) => {
return {
'@defaults transform': {},
'--tw-scale-y': `scaleY(${value})`,
transform: cssTransformValue,
}
},
},
{ values: theme('scale'), supportsNegativeValues: true }
)
},
transform: ({ addDefaults, addUtilities }) => { transform: ({ addDefaults, addUtilities }) => {
addDefaults('transform', { addDefaults('transform', {
'--tw-translate-x': '0', '--tw-translate-x': ' ',
'--tw-translate-y': '0', '--tw-translate-y': ' ',
'--tw-rotate': '0', '--tw-rotate': ' ',
'--tw-skew-x': '0', '--tw-skew-x': ' ',
'--tw-skew-y': '0', '--tw-skew-y': ' ',
'--tw-scale-x': '1', '--tw-scale-x': ' ',
'--tw-scale-y': '1', '--tw-scale-y': ' ',
}) })
addUtilities({ addUtilities({
@ -913,7 +969,7 @@ export let corePlugins = {
}, },
animation: ({ matchUtilities, theme, config }) => { animation: ({ matchUtilities, theme, config }) => {
let prefixName = (name) => `${config('prefix')}${escapeClassName(name)}` let prefixName = (name) => escapeClassName(config('prefix') + name)
let keyframes = Object.fromEntries( let keyframes = Object.fromEntries(
Object.entries(theme('keyframes') ?? {}).map(([key, value]) => { Object.entries(theme('keyframes') ?? {}).map(([key, value]) => {
return [key, { [`@keyframes ${prefixName(key)}`]: value }] return [key, { [`@keyframes ${prefixName(key)}`]: value }]
@ -1103,6 +1159,7 @@ export let corePlugins = {
appearance: ({ addUtilities }) => { appearance: ({ addUtilities }) => {
addUtilities({ addUtilities({
'.appearance-none': { appearance: 'none' }, '.appearance-none': { appearance: 'none' },
'.appearance-auto': { appearance: 'auto' },
}) })
}, },
@ -1255,13 +1312,13 @@ export let corePlugins = {
], ],
]), ]),
space: ({ matchUtilities, addUtilities, theme }) => { space: ({ matchUtilities, addUtilities, theme, config }) => {
matchUtilities( matchUtilities(
{ {
'space-x': (value) => { 'space-x': (value) => {
value = value === '0' ? '0px' : value value = value === '0' ? '0px' : value
if (__OXIDE__) { if (flagEnabled(config(), 'logicalSiblingUtilities')) {
return { return {
'& > :not([hidden]) ~ :not([hidden])': { '& > :not([hidden]) ~ :not([hidden])': {
'--tw-space-x-reverse': '0', '--tw-space-x-reverse': '0',
@ -1300,13 +1357,13 @@ export let corePlugins = {
}) })
}, },
divideWidth: ({ matchUtilities, addUtilities, theme }) => { divideWidth: ({ matchUtilities, addUtilities, theme, config }) => {
matchUtilities( matchUtilities(
{ {
'divide-x': (value) => { 'divide-x': (value) => {
value = value === '0' ? '0px' : value value = value === '0' ? '0px' : value
if (__OXIDE__) { if (flagEnabled(config(), 'logicalSiblingUtilities')) {
return { return {
'& > :not([hidden]) ~ :not([hidden])': { '& > :not([hidden]) ~ :not([hidden])': {
'@defaults border-width': {}, '@defaults border-width': {},
@ -1503,6 +1560,15 @@ export let corePlugins = {
}) })
}, },
textWrap: ({ addUtilities }) => {
addUtilities({
'.text-wrap': { 'text-wrap': 'wrap' },
'.text-nowrap': { 'text-wrap': 'nowrap' },
'.text-balance': { 'text-wrap': 'balance' },
'.text-pretty': { 'text-wrap': 'pretty' },
})
},
wordBreak: ({ addUtilities }) => { wordBreak: ({ addUtilities }) => {
addUtilities({ addUtilities({
'.break-normal': { 'overflow-wrap': 'normal', 'word-break': 'normal' }, '.break-normal': { 'overflow-wrap': 'normal', 'word-break': 'normal' },
@ -2846,4 +2912,10 @@ export let corePlugins = {
content: createUtilityPlugin('content', [ content: createUtilityPlugin('content', [
['content', ['--tw-content', ['content', 'var(--tw-content)']]], ['content', ['--tw-content', ['content', 'var(--tw-content)']]],
]), ]),
forcedColorAdjust: ({ addUtilities }) => {
addUtilities({
'.forced-color-adjust-auto': { 'forced-color-adjust': 'auto' },
'.forced-color-adjust-none': { 'forced-color-adjust': 'none' },
})
},
} }

View file

@ -24,16 +24,19 @@
4. Use the user's configured `sans` font-family by default. 4. Use the user's configured `sans` font-family by default.
5. Use the user's configured `sans` font-feature-settings by default. 5. Use the user's configured `sans` font-feature-settings by default.
6. Use the user's configured `sans` font-variation-settings by default. 6. Use the user's configured `sans` font-variation-settings by default.
7. Disable tap highlights on iOS.
*/ */
html { html,
:host {
line-height: 1.5; /* 1 */ line-height: 1.5; /* 1 */
-webkit-text-size-adjust: 100%; /* 2 */ -webkit-text-size-adjust: 100%; /* 2 */
-moz-tab-size: 4; /* 3 */ -moz-tab-size: 4; /* 3 */
tab-size: 4; /* 3 */ tab-size: 4; /* 3 */
font-family: theme('fontFamily.sans', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"); /* 4 */ font-family: theme('fontFamily.sans', ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"); /* 4 */
font-feature-settings: theme('fontFamily.sans[1].fontFeatureSettings', normal); /* 5 */ font-feature-settings: theme('fontFamily.sans[1].fontFeatureSettings', normal); /* 5 */
font-variation-settings: theme('fontFamily.sans[1].fontVariationSettings', normal); /* 6 */ font-variation-settings: theme('fontFamily.sans[1].fontVariationSettings', normal); /* 6 */
-webkit-tap-highlight-color: transparent; /* 7 */
} }
/* /*
@ -99,8 +102,10 @@ strong {
} }
/* /*
1. Use the user's configured `mono` font family by default. 1. Use the user's configured `mono` font-family by default.
2. Correct the odd `em` font sizing in all browsers. 2. Use the user's configured `mono` font-feature-settings by default.
3. Use the user's configured `mono` font-variation-settings by default.
4. Correct the odd `em` font sizing in all browsers.
*/ */
code, code,
@ -108,7 +113,9 @@ kbd,
samp, samp,
pre { pre {
font-family: theme('fontFamily.mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace); /* 1 */ font-family: theme('fontFamily.mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace); /* 1 */
font-size: 1em; /* 2 */ font-feature-settings: theme('fontFamily.mono[1].fontFeatureSettings', normal); /* 2 */
font-variation-settings: theme('fontFamily.mono[1].fontVariationSettings', normal); /* 3 */
font-size: 1em; /* 4 */
} }
/* /*
@ -163,6 +170,8 @@ optgroup,
select, select,
textarea { textarea {
font-family: inherit; /* 1 */ font-family: inherit; /* 1 */
font-feature-settings: inherit; /* 1 */
font-variation-settings: inherit; /* 1 */
font-size: 100%; /* 1 */ font-size: 100%; /* 1 */
font-weight: inherit; /* 1 */ font-weight: inherit; /* 1 */
line-height: inherit; /* 1 */ line-height: inherit; /* 1 */
@ -264,7 +273,7 @@ summary {
} }
/* /*
Removes the default spacing and border for appropriate elements. Removes the default spacing for appropriate elements.
*/ */
blockquote, blockquote,
@ -300,6 +309,10 @@ menu {
padding: 0; padding: 0;
} }
dialog {
padding: 0;
}
/* /*
Prevent resizing textareas horizontally by default. Prevent resizing textareas horizontally by default.
*/ */
@ -331,6 +344,7 @@ button,
/* /*
Make sure disabled buttons don't get the pointer cursor. Make sure disabled buttons don't get the pointer cursor.
*/ */
:disabled { :disabled {
cursor: default; cursor: default;
} }
@ -363,7 +377,10 @@ video {
height: auto; height: auto;
} }
/* Make elements with the HTML hidden attribute stay hidden by default */ /*
Make elements with the HTML hidden attribute stay hidden by default.
*/
[hidden] { [hidden] {
display: none; display: none;
} }

View file

@ -3,27 +3,21 @@ import log from './util/log'
let defaults = { let defaults = {
optimizeUniversalDefaults: false, optimizeUniversalDefaults: false,
generalizedModifiers: true, disableColorOpacityUtilitiesByDefault: false,
get disableColorOpacityUtilitiesByDefault() { relativeContentPathsByDefault: false,
return __OXIDE__ oxideParser: true,
}, logicalSiblingUtilities: false,
get relativeContentPathsByDefault() {
return __OXIDE__
},
} }
let featureFlags = { export let featureFlags = {
future: [ future: [
'hoverOnlyWhenSupported', 'hoverOnlyWhenSupported',
'respectDefaultRingColorOpacity', 'respectDefaultRingColorOpacity',
'disableColorOpacityUtilitiesByDefault', 'disableColorOpacityUtilitiesByDefault',
'relativeContentPathsByDefault', 'relativeContentPathsByDefault',
'logicalSiblingUtilities',
], ],
experimental: [ experimental: ['optimizeUniversalDefaults', 'oxideParser'],
'optimizeUniversalDefaults',
'generalizedModifiers',
// 'variantGrouping',
],
} }
export function flagEnabled(config, flag) { export function flagEnabled(config, flag) {

View file

@ -7,10 +7,40 @@ import fastGlob from 'fast-glob'
import normalizePath from 'normalize-path' import normalizePath from 'normalize-path'
import { parseGlob } from '../util/parseGlob' import { parseGlob } from '../util/parseGlob'
import { env } from './sharedState' import { env } from './sharedState'
import { resolveContentPaths } from '@tailwindcss/oxide'
/** @typedef {import('../../types/config.js').RawFile} RawFile */ /** @typedef {import('../../types/config.js').RawFile} RawFile */
/** @typedef {import('../../types/config.js').FilePath} FilePath */ /** @typedef {import('../../types/config.js').FilePath} FilePath */
/*
* @param {import('tailwindcss').Config} tailwindConfig
* @param {{skip:string[]}} options
* @returns {ContentPath[]}
*/
function resolveContentFiles(tailwindConfig, { skip = [] } = {}) {
if (
Array.isArray(tailwindConfig.content.files) &&
tailwindConfig.content.files.includes('auto')
) {
let idx = tailwindConfig.content.files.indexOf('auto')
if (idx !== -1) {
env.DEBUG && console.time('Calculating resolve content paths')
let resolved = resolveContentPaths({ base: process.cwd() })
env.DEBUG && console.timeEnd('Calculating resolve content paths')
tailwindConfig.content.files.splice(idx, 1, ...resolved)
}
}
if (skip.length > 0) {
tailwindConfig.content.files = tailwindConfig.content.files.filter(
(filePath) => !skip.includes(filePath)
)
}
return tailwindConfig.content.files
}
/** /**
* @typedef {object} ContentPath * @typedef {object} ContentPath
* @property {string} original * @property {string} original
@ -32,7 +62,9 @@ import { env } from './sharedState'
* @returns {ContentPath[]} * @returns {ContentPath[]}
*/ */
export function parseCandidateFiles(context, tailwindConfig) { export function parseCandidateFiles(context, tailwindConfig) {
let files = tailwindConfig.content.files let files = resolveContentFiles(tailwindConfig, {
skip: [context.userConfigPath],
})
// Normalize the file globs // Normalize the file globs
files = files.filter((filePath) => typeof filePath === 'string') files = files.filter((filePath) => typeof filePath === 'string')

View file

@ -1,4 +1,3 @@
import { flagEnabled } from '../featureFlags'
import * as regex from './regex' import * as regex from './regex'
export function defaultExtractor(context) { export function defaultExtractor(context) {
@ -12,16 +11,17 @@ export function defaultExtractor(context) {
let results = [] let results = []
for (let pattern of patterns) { for (let pattern of patterns) {
results = [...results, ...(content.match(pattern) ?? [])] for (let result of content.match(pattern) ?? []) {
results.push(clipAtBalancedParens(result))
}
} }
return results.filter((v) => v !== undefined).map(clipAtBalancedParens) return results
} }
} }
function* buildRegExps(context) { function* buildRegExps(context) {
let separator = context.tailwindConfig.separator let separator = context.tailwindConfig.separator
let variantGroupingEnabled = flagEnabled(context.tailwindConfig, 'variantGrouping')
let prefix = let prefix =
context.tailwindConfig.prefix !== '' context.tailwindConfig.prefix !== ''
? regex.optional(regex.pattern([/-?/, regex.escape(context.tailwindConfig.prefix)])) ? regex.optional(regex.pattern([/-?/, regex.escape(context.tailwindConfig.prefix)]))
@ -35,7 +35,7 @@ function* buildRegExps(context) {
// This is a targeted fix to continue to allow theme() // This is a targeted fix to continue to allow theme()
// with square brackets to work in arbitrary properties // with square brackets to work in arbitrary properties
// while fixing a problem with the regex matching too much // while fixing a problem with the regex matching too much
/\[[^\s:'"`]+:[^\s]+?\[[^\s]+\][^\s]+?\]/, /\[[^\s:'"`\]]+:[^\s]+?\[[^\s]+\][^\s]+?\]/,
// Utilities // Utilities
regex.pattern([ regex.pattern([
@ -47,7 +47,7 @@ function* buildRegExps(context) {
regex.any([ regex.any([
regex.pattern([ regex.pattern([
// Arbitrary values // Arbitrary values
/-(?:\w+-)*\[[^\s:]+\]/, /-(?:\w+-)*\[(?:[^\s\[\]]+\[[^\s\[\]]+\])*[^\s:\[\]]+\]/,
// Not immediately followed by an `{[(` // Not immediately followed by an `{[(`
/(?![{([]])/, /(?![{([]])/,
@ -58,7 +58,7 @@ function* buildRegExps(context) {
regex.pattern([ regex.pattern([
// Arbitrary values // Arbitrary values
/-(?:\w+-)*\[[^\s]+\]/, /-(?:\w+-)*\[(?:[^\s\[\]]+\[[^\s\[\]]+\])*[^\s:\[\]]+\]/,
// Not immediately followed by an `{[(` // Not immediately followed by an `{[(`
/(?![{([]])/, /(?![{([]])/,
@ -80,12 +80,18 @@ function* buildRegExps(context) {
// This is here to provide special support for the `@` variant // This is here to provide special support for the `@` variant
regex.pattern([/@\[[^\s"'`]+\](\/[^\s"'`]+)?/, separator]), regex.pattern([/@\[[^\s"'`]+\](\/[^\s"'`]+)?/, separator]),
// With variant modifier (e.g.: group-[..]/modifier)
regex.pattern([/([^\s"'`\[\\]+-)?\[[^\s"'`]+\]\/\w+/, separator]),
regex.pattern([/([^\s"'`\[\\]+-)?\[[^\s"'`]+\]/, separator]), regex.pattern([/([^\s"'`\[\\]+-)?\[[^\s"'`]+\]/, separator]),
regex.pattern([/[^\s"'`\[\\]+/, separator]), regex.pattern([/[^\s"'`\[\\]+/, separator]),
]), ]),
// With quotes allowed // With quotes allowed
regex.any([ regex.any([
// With variant modifier (e.g.: group-[..]/modifier)
regex.pattern([/([^\s"'`\[\\]+-)?\[[^\s`]+\]\/\w+/, separator]),
regex.pattern([/([^\s"'`\[\\]+-)?\[[^\s`]+\]/, separator]), regex.pattern([/([^\s"'`\[\\]+-)?\[[^\s`]+\]/, separator]),
regex.pattern([/[^\s`\[\\]+/, separator]), regex.pattern([/[^\s`\[\\]+/, separator]),
]), ]),
@ -103,15 +109,7 @@ function* buildRegExps(context) {
prefix, prefix,
variantGroupingEnabled utility,
? regex.any([
// Or any of those things but grouped separated by commas
regex.pattern([/\(/, utility, regex.zeroOrMore([/,/, utility]), /\)/]),
// Arbitrary properties, constrained utilities, arbitrary values, etc…
utility,
])
: utility,
]) ])
} }

View file

@ -1,7 +1,7 @@
import dlv from 'dlv' import dlv from 'dlv'
import didYouMean from 'didyoumean' import didYouMean from 'didyoumean'
import transformThemeValue from '../util/transformThemeValue' import transformThemeValue from '../util/transformThemeValue'
import parseValue from 'postcss-value-parser' import parseValue from '../value-parser/index'
import { normalizeScreens } from '../util/normalizeScreens' import { normalizeScreens } from '../util/normalizeScreens'
import buildMediaQuery from '../util/buildMediaQuery' import buildMediaQuery from '../util/buildMediaQuery'
import { toPath } from '../util/toPath' import { toPath } from '../util/toPath'
@ -146,6 +146,9 @@ function resolveVNode(node, vNode, functions) {
} }
function resolveFunctions(node, input, functions) { function resolveFunctions(node, input, functions) {
let hasAnyFn = Object.keys(functions).some((fn) => input.includes(`${fn}(`))
if (!hasAnyFn) return input
return parseValue(input) return parseValue(input)
.walk((vNode) => { .walk((vNode) => {
resolveVNode(node, vNode, functions) resolveVNode(node, vNode, functions)

View file

@ -553,6 +553,13 @@ function processApply(root, context, localCache) {
? parent.selector.slice(importantSelector.length) ? parent.selector.slice(importantSelector.length)
: parent.selector : parent.selector
// If the selector becomes empty after replacing the important selector
// This means that it's the same as the parent selector and we don't want to replace it
// Otherwise we'll crash
if (parentSelector === '') {
parentSelector = parent.selector
}
rule.selector = replaceSelector(parentSelector, rule.selector, applyCandidate) rule.selector = replaceSelector(parentSelector, rule.selector, applyCandidate)
// And then re-add it if it was removed // And then re-add it if it was removed

View file

@ -1,10 +1,12 @@
import fs from 'fs' import fs from 'fs'
import LRU from '@alloc/quick-lru' import LRU from '@alloc/quick-lru'
import { parseCandidateStrings, IO, Parsing } from '@tailwindcss/oxide'
import * as sharedState from './sharedState' import * as sharedState from './sharedState'
import { generateRules } from './generateRules' import { generateRules } from './generateRules'
import log from '../util/log' import log from '../util/log'
import cloneNodes from '../util/cloneNodes' import cloneNodes from '../util/cloneNodes'
import { defaultExtractor } from './defaultExtractor' import { defaultExtractor } from './defaultExtractor'
import { flagEnabled } from '../featureFlags'
let env = sharedState.env let env = sharedState.env
@ -24,7 +26,10 @@ function getExtractor(context, fileExtension) {
extractors[fileExtension] || extractors[fileExtension] ||
extractors.DEFAULT || extractors.DEFAULT ||
builtInExtractors[fileExtension] || builtInExtractors[fileExtension] ||
builtInExtractors.DEFAULT(context) // Because we call `DEFAULT(context)`, the returning function is always a new function without a
// stable identity. Marking it with `DEFAULT_EXTRACTOR` allows us to check if it is the default
// extractor without relying on the function identity.
Object.assign(builtInExtractors.DEFAULT(context), { DEFAULT_EXTRACTOR: true })
) )
} }
@ -98,7 +103,7 @@ function buildStylesheet(rules, context) {
} }
export default function expandTailwindAtRules(context) { export default function expandTailwindAtRules(context) {
return (root) => { return async (root) => {
let layerNodes = { let layerNodes = {
base: null, base: null,
components: null, components: null,
@ -130,27 +135,51 @@ export default function expandTailwindAtRules(context) {
env.DEBUG && console.time('Reading changed files') env.DEBUG && console.time('Reading changed files')
if (__OXIDE__) { /** @type {[item: {file?: string, content?: string}, meta: {transformer: any, extractor: any}][]} */
// TODO: Pass through or implement `extractor` let regexParserContent = []
for (let candidate of require('@tailwindcss/oxide').parseCandidateStringsFromFiles(
context.changedContent /** @type {{file?: string, content?: string}[]} */
// Object.assign({}, builtInTransformers, context.tailwindConfig.content.transform) let rustParserContent = []
for (let item of context.changedContent) {
let transformer = getTransformer(context.tailwindConfig, item.extension)
let extractor = getExtractor(context, item.extension)
if (
flagEnabled(context.tailwindConfig, 'oxideParser') &&
transformer === builtInTransformers.DEFAULT &&
extractor?.DEFAULT_EXTRACTOR === true
) {
rustParserContent.push(item)
} else {
regexParserContent.push([item, { transformer, extractor }])
}
}
// Read files using our newer, faster parser when:
// - Oxide is enabled; AND
// - The file is using default transfomers and extractors
if (rustParserContent.length > 0) {
for (let candidate of parseCandidateStrings(
rustParserContent,
IO.Parallel | Parsing.Parallel
)) { )) {
candidates.add(candidate) candidates.add(candidate)
} }
}
// for (let { file, content, extension } of context.changedContent) { // Otherwise, read any files in node and parse with regexes
// let transformer = getTransformer(context.tailwindConfig, extension) const BATCH_SIZE = 500
// let extractor = getExtractor(context, extension)
// getClassCandidatesOxide(file, transformer(content), extractor, candidates, seen) for (let i = 0; i < regexParserContent.length; i += BATCH_SIZE) {
// } let batch = regexParserContent.slice(i, i + BATCH_SIZE)
} else {
for (let { file, content, extension } of context.changedContent) { await Promise.all(
let transformer = getTransformer(context.tailwindConfig, extension) batch.map(async ([{ file, content }, { transformer, extractor }]) => {
let extractor = getExtractor(context, extension) content = file ? await fs.promises.readFile(file, 'utf8') : content
content = file ? fs.readFileSync(file, 'utf8') : content getClassCandidates(transformer(content), extractor, candidates, seen)
getClassCandidates(transformer(content), extractor, candidates, seen) })
} )
} }
env.DEBUG && console.timeEnd('Reading changed files') env.DEBUG && console.timeEnd('Reading changed files')
@ -162,15 +191,16 @@ export default function expandTailwindAtRules(context) {
env.DEBUG && console.time('Generate rules') env.DEBUG && console.time('Generate rules')
env.DEBUG && console.time('Sorting candidates') env.DEBUG && console.time('Sorting candidates')
let sortedCandidates = __OXIDE__ // TODO: only sort if we are not using the oxide parser (flagEnabled(context.tailwindConfig,
? candidates // 'oxideParser')) AND if we got all the candidates form the oxideParser alone. This will not
: new Set( // be the case currently if you have custom transformers / extractors.
[...candidates].sort((a, z) => { let sortedCandidates = new Set(
if (a === z) return 0 [...candidates].sort((a, z) => {
if (a < z) return -1 if (a === z) return 0
return 1 if (a < z) return -1
}) return 1
) })
)
env.DEBUG && console.timeEnd('Sorting candidates') env.DEBUG && console.timeEnd('Sorting candidates')
generateRules(sortedCandidates, context) generateRules(sortedCandidates, context)
env.DEBUG && console.timeEnd('Generate rules') env.DEBUG && console.timeEnd('Generate rules')
@ -244,13 +274,26 @@ export default function expandTailwindAtRules(context) {
) )
layerNodes.variants.remove() layerNodes.variants.remove()
} else if (variantNodes.length > 0) { } else if (variantNodes.length > 0) {
root.append( let cloned = cloneNodes(variantNodes, undefined, {
cloneNodes(variantNodes, root.source, { layer: 'variants',
layer: 'variants', })
cloned.forEach((node) => {
let parentLayer = node.raws.tailwind?.parentLayer ?? null
node.walk((n) => {
if (!n.source) {
n.source = layerNodes[parentLayer].source
}
}) })
) })
root.append(cloned)
} }
// TODO: Why is the root node having no source location for `end` possible?
root.source.end = root.source.end ?? root.source.start
// If we've got a utility layer and no utilities are generated there's likely something wrong // If we've got a utility layer and no utilities are generated there's likely something wrong
const hasUtilityVariants = variantNodes.some( const hasUtilityVariants = variantNodes.some(
(node) => node.raws.tailwind?.parentLayer === 'utilities' (node) => node.raws.tailwind?.parentLayer === 'utilities'

View file

@ -13,10 +13,9 @@ import {
} from '../util/formatVariantSelector' } from '../util/formatVariantSelector'
import { asClass } from '../util/nameClass' import { asClass } from '../util/nameClass'
import { normalize } from '../util/dataTypes' import { normalize } from '../util/dataTypes'
import { isValidVariantFormatString, parseVariant } from './setupContextUtils' import { isValidVariantFormatString, parseVariant, INTERNAL_FEATURES } from './setupContextUtils'
import isValidArbitraryValue from '../util/isSyntacticallyValidPropertyValue' import isValidArbitraryValue from '../util/isSyntacticallyValidPropertyValue'
import { splitAtTopLevelOnly } from '../util/splitAtTopLevelOnly.js' import { splitAtTopLevelOnly } from '../util/splitAtTopLevelOnly.js'
import { flagEnabled } from '../featureFlags'
import { applyImportantSelector } from '../util/applyImportantSelector' import { applyImportantSelector } from '../util/applyImportantSelector'
let classNameParser = selectorParser((selectors) => { let classNameParser = selectorParser((selectors) => {
@ -45,7 +44,7 @@ function* candidatePermutations(candidate) {
let bracketIdx = candidate.indexOf('[') let bracketIdx = candidate.indexOf('[')
// If character before `[` isn't a dash or a slash, this isn't a dynamic class // If character before `[` isn't a dash or a slash, this isn't a dynamic class
// eg. string[] // e.g. string[]
if (candidate[bracketIdx - 1] === '-') { if (candidate[bracketIdx - 1] === '-') {
dashIdx = bracketIdx - 1 dashIdx = bracketIdx - 1
} else if (candidate[bracketIdx - 1] === '/') { } else if (candidate[bracketIdx - 1] === '/') {
@ -175,10 +174,6 @@ function applyVariant(variant, matches, context) {
if (modifiers.length && !context.variantMap.has(variant)) { if (modifiers.length && !context.variantMap.has(variant)) {
variant = baseVariant variant = baseVariant
args.modifier = modifiers[0] args.modifier = modifiers[0]
if (!flagEnabled(context.tailwindConfig, 'generalizedModifiers')) {
return []
}
} }
} }
@ -193,13 +188,13 @@ function applyVariant(variant, matches, context) {
// group[:hover] (`-` is missing) // group[:hover] (`-` is missing)
let match = /(.)(-?)\[(.*)\]/g.exec(variant) let match = /(.)(-?)\[(.*)\]/g.exec(variant)
if (match) { if (match) {
let [, char, seperator, value] = match let [, char, separator, value] = match
// @-[200px] case // @-[200px] case
if (char === '@' && seperator === '-') return [] if (char === '@' && separator === '-') return []
// group[:hover] case // group[:hover] case
if (char !== '@' && seperator === '') return [] if (char !== '@' && separator === '') return []
variant = variant.replace(`${seperator}[${value}]`, '') variant = variant.replace(`${separator}[${value}]`, '')
args.value = value args.value = value
} }
} }
@ -230,9 +225,16 @@ function applyVariant(variant, matches, context) {
if (context.variantMap.has(variant)) { if (context.variantMap.has(variant)) {
let isArbitraryVariant = isArbitraryValue(variant) let isArbitraryVariant = isArbitraryValue(variant)
let internalFeatures = context.variantOptions.get(variant)?.[INTERNAL_FEATURES] ?? {}
let variantFunctionTuples = context.variantMap.get(variant).slice() let variantFunctionTuples = context.variantMap.get(variant).slice()
let result = [] let result = []
let respectPrefix = (() => {
if (isArbitraryVariant) return false
if (internalFeatures.respectPrefix === false) return false
return true
})()
for (let [meta, rule] of matches) { for (let [meta, rule] of matches) {
// Don't generate variants for user css // Don't generate variants for user css
if (meta.layer === 'user') { if (meta.layer === 'user') {
@ -293,7 +295,7 @@ function applyVariant(variant, matches, context) {
format(selectorFormat) { format(selectorFormat) {
collectedFormats.push({ collectedFormats.push({
format: selectorFormat, format: selectorFormat,
isArbitraryVariant, respectPrefix,
}) })
}, },
args, args,
@ -322,7 +324,7 @@ function applyVariant(variant, matches, context) {
if (typeof ruleWithVariant === 'string') { if (typeof ruleWithVariant === 'string') {
collectedFormats.push({ collectedFormats.push({
format: ruleWithVariant, format: ruleWithVariant,
isArbitraryVariant, respectPrefix,
}) })
} }
@ -366,7 +368,7 @@ function applyVariant(variant, matches, context) {
// format: .foo & // format: .foo &
collectedFormats.push({ collectedFormats.push({
format: modified.replace(rebuiltBase, '&'), format: modified.replace(rebuiltBase, '&'),
isArbitraryVariant, respectPrefix,
}) })
rule.selector = before rule.selector = before
}) })
@ -459,9 +461,9 @@ function isParsableNode(node) {
} }
function isParsableCssValue(property, value) { function isParsableCssValue(property, value) {
// We don't want to to treat [https://example.com] as a custom property // We don't want to treat [https://example.com] as a custom property
// Even though, according to the CSS grammar, it's a totally valid CSS declaration // Even though, according to the CSS grammar, it's a totally valid CSS declaration
// So we short-circuit here by checking if the custom property looks like a url // So we short-circuit here by checking if the custom property looks like a URL
if (looksLikeUri(`${property}:${value}`)) { if (looksLikeUri(`${property}:${value}`)) {
return false return false
} }
@ -489,7 +491,7 @@ function extractArbitraryProperty(classCandidate, context) {
return null return null
} }
let normalized = normalize(value) let normalized = normalize(value, { property })
if (!isParsableCssValue(property, normalized)) { if (!isParsableCssValue(property, normalized)) {
return null return null
@ -566,7 +568,7 @@ function* recordCandidates(matches, classCandidate) {
} }
} }
function* resolveMatches(candidate, context, original = candidate) { function* resolveMatches(candidate, context) {
let separator = context.tailwindConfig.separator let separator = context.tailwindConfig.separator
let [classCandidate, ...variants] = splitWithSeparator(candidate, separator).reverse() let [classCandidate, ...variants] = splitWithSeparator(candidate, separator).reverse()
let important = false let important = false
@ -576,15 +578,6 @@ function* resolveMatches(candidate, context, original = candidate) {
classCandidate = classCandidate.slice(1) classCandidate = classCandidate.slice(1)
} }
if (flagEnabled(context.tailwindConfig, 'variantGrouping')) {
if (classCandidate.startsWith('(') && classCandidate.endsWith(')')) {
let base = variants.slice().reverse().join(separator)
for (let part of splitAtTopLevelOnly(classCandidate.slice(1, -1), ',')) {
yield* resolveMatches(base + separator + part, context, original)
}
}
}
// TODO: Reintroduce this in ways that doesn't break on false positives // TODO: Reintroduce this in ways that doesn't break on false positives
// function sortAgainst(toSort, against) { // function sortAgainst(toSort, against) {
// return toSort.slice().sort((a, z) => { // return toSort.slice().sort((a, z) => {
@ -773,7 +766,7 @@ function* resolveMatches(candidate, context, original = candidate) {
match[1].raws.tailwind = { ...match[1].raws.tailwind, candidate } match[1].raws.tailwind = { ...match[1].raws.tailwind, candidate }
// Apply final format selector // Apply final format selector
match = applyFinalFormat(match, { context, candidate, original }) match = applyFinalFormat(match, { context, candidate })
// Skip rules with invalid selectors // Skip rules with invalid selectors
// This will cause the candidate to be added to the "not class" // This will cause the candidate to be added to the "not class"
@ -787,7 +780,7 @@ function* resolveMatches(candidate, context, original = candidate) {
} }
} }
function applyFinalFormat(match, { context, candidate, original }) { function applyFinalFormat(match, { context, candidate }) {
if (!match[0].collectedFormats) { if (!match[0].collectedFormats) {
return match return match
} }
@ -822,10 +815,19 @@ function applyFinalFormat(match, { context, candidate, original }) {
} }
try { try {
rule.selector = finalizeSelector(rule.selector, finalFormat, { let selector = finalizeSelector(rule.selector, finalFormat, {
candidate: original, candidate,
context, context,
}) })
// Finalize Selector determined that this candidate is irrelevant
// TODO: This elimination should happen earlier so this never happens
if (selector === null) {
rule.remove()
return
}
rule.selector = selector
} catch { } catch {
// If this selector is invalid we also want to skip it // If this selector is invalid we also want to skip it
// But it's likely that being invalid here means there's a bug in a plugin rather than too loosely matching content // But it's likely that being invalid here means there's a bug in a plugin rather than too loosely matching content
@ -838,6 +840,11 @@ function applyFinalFormat(match, { context, candidate, original }) {
return null return null
} }
// If all rules have been eliminated we can skip this candidate entirely
if (container.nodes.length === 0) {
return null
}
match[1] = container.nodes[0] match[1] = container.nodes[0]
return match return match
@ -875,7 +882,7 @@ function getImportantStrategy(important) {
} }
} }
function generateRules(candidates, context) { function generateRules(candidates, context, isSorting = false) {
let allRules = [] let allRules = []
let strategy = getImportantStrategy(context.tailwindConfig.important) let strategy = getImportantStrategy(context.tailwindConfig.important)
@ -910,7 +917,9 @@ function generateRules(candidates, context) {
rule = container.nodes[0] rule = container.nodes[0]
} }
let newEntry = [sort, rule] // Note: We have to clone rules during sorting
// so we eliminate some shared mutable state
let newEntry = [sort, isSorting ? rule.clone() : rule]
rules.add(newEntry) rules.add(newEntry)
context.ruleCache.add(newEntry) context.ruleCache.add(newEntry)
allRules.push(newEntry) allRules.push(newEntry)

View file

@ -0,0 +1,34 @@
import postcss from 'postcss'
import postcssImport from 'postcss-import'
const TAILWIND = Symbol()
export function handleImportAtRules() {
let RESTORE_ATRULE_COMMENT = '__TAILWIND_RESTORE__'
let atRulesToRestore = ['tailwind', 'config']
return [
(root) => {
root.walkAtRules((rule) => {
if (!atRulesToRestore.includes(rule.name)) return rule
rule.after(
postcss.comment({
text: RESTORE_ATRULE_COMMENT,
raws: { [TAILWIND]: { rule } },
})
)
rule.remove()
})
},
postcssImport(),
(root) => {
root.walkComments((rule) => {
if (rule.text === RESTORE_ATRULE_COMMENT) {
rule.after(rule.raws[TAILWIND].rule)
rule.remove()
}
})
},
]
}

View file

@ -4,6 +4,14 @@ import { transform } from 'sucrase'
import { Config } from '../../types/config' import { Config } from '../../types/config'
let jiti: ReturnType<typeof jitiFactory> | null = null let jiti: ReturnType<typeof jitiFactory> | null = null
// @internal
// This WILL be removed in some future release
// If you rely on this your stuff WILL break
export function useCustomJiti(_jiti: ReturnType<typeof jitiFactory>) {
jiti = _jiti
}
function lazyJiti() { function lazyJiti() {
return ( return (
jiti ?? jiti ??

View file

@ -10,33 +10,6 @@ export default function normalizeTailwindDirectives(root) {
applyDirectives.add(atRule) applyDirectives.add(atRule)
} }
if (atRule.name === 'import') {
if (atRule.params === '"tailwindcss/base"' || atRule.params === "'tailwindcss/base'") {
atRule.name = 'tailwind'
atRule.params = 'base'
} else if (
atRule.params === '"tailwindcss/components"' ||
atRule.params === "'tailwindcss/components'"
) {
atRule.name = 'tailwind'
atRule.params = 'components'
} else if (
atRule.params === '"tailwindcss/utilities"' ||
atRule.params === "'tailwindcss/utilities'"
) {
atRule.name = 'tailwind'
atRule.params = 'utilities'
} else if (
atRule.params === '"tailwindcss/screens"' ||
atRule.params === "'tailwindcss/screens'" ||
atRule.params === '"tailwindcss/variants"' ||
atRule.params === "'tailwindcss/variants'"
) {
atRule.name = 'tailwind'
atRule.params = 'variants'
}
}
if (atRule.name === 'tailwind') { if (atRule.name === 'tailwind') {
if (atRule.params === 'screens') { if (atRule.params === 'screens') {
atRule.params = 'variants' atRule.params = 'variants'

View file

@ -23,7 +23,7 @@ import { remapBitfield } from './remap-bitfield.js'
* @property {bigint} arbitrary 0n if false, 1n if true * @property {bigint} arbitrary 0n if false, 1n if true
* @property {bigint} variants Dynamic size. 1 bit per registered variant. 0n means no variants * @property {bigint} variants Dynamic size. 1 bit per registered variant. 0n means no variants
* @property {bigint} parallelIndex Rule index for the parallel variant. 0 if not applicable. * @property {bigint} parallelIndex Rule index for the parallel variant. 0 if not applicable.
* @property {bigint} index Index of the rule / utility in it's given *parent* layer. Monotonically increasing. * @property {bigint} index Index of the rule / utility in its given *parent* layer. Monotonically increasing.
* @property {VariantOption[]} options Some information on how we can sort arbitrary variants * @property {VariantOption[]} options Some information on how we can sort arbitrary variants
*/ */
@ -302,7 +302,7 @@ export class Offsets {
let mapping = this.recalculateVariantOffsets() let mapping = this.recalculateVariantOffsets()
// No arbitrary variants? Nothing to do. // No arbitrary variants? Nothing to do.
// Everyhing already in order? Nothing to do. // Everything already in order? Nothing to do.
if (mapping.length === 0) { if (mapping.length === 0) {
return list return list
} }
@ -354,7 +354,7 @@ function max(nums) {
* Using `.sort()` without a custom compare function is faster * Using `.sort()` without a custom compare function is faster
* But you can only use that if you're sorting an array of * But you can only use that if you're sorting an array of
* only strings. If you're sorting strings inside objects * only strings. If you're sorting strings inside objects
* or arrays, you need must use a custom compare function. * or arrays, you need to use a custom compare function.
* *
* @param {string} a * @param {string} a
* @param {string} b * @param {string} b

View file

@ -21,9 +21,10 @@ import isSyntacticallyValidPropertyValue from '../util/isSyntacticallyValidPrope
import { generateRules, getClassNameFromSelector } from './generateRules' import { generateRules, getClassNameFromSelector } from './generateRules'
import { hasContentChanged } from './cacheInvalidation.js' import { hasContentChanged } from './cacheInvalidation.js'
import { Offsets } from './offsets.js' import { Offsets } from './offsets.js'
import { flagEnabled } from '../featureFlags.js'
import { finalizeSelector, formatVariantSelector } from '../util/formatVariantSelector' import { finalizeSelector, formatVariantSelector } from '../util/formatVariantSelector'
export const INTERNAL_FEATURES = Symbol()
const VARIANT_TYPES = { const VARIANT_TYPES = {
AddVariant: Symbol.for('ADD_VARIANT'), AddVariant: Symbol.for('ADD_VARIANT'),
MatchVariant: Symbol.for('MATCH_VARIANT'), MatchVariant: Symbol.for('MATCH_VARIANT'),
@ -146,43 +147,45 @@ function getClasses(selector, mutate) {
return parser.transformSync(selector) return parser.transformSync(selector)
} }
/**
* Ignore everything inside a :not(...). This allows you to write code like
* `div:not(.foo)`. If `.foo` is never found in your code, then we used to
* not generated it. But now we will ignore everything inside a `:not`, so
* that it still gets generated.
*
* @param {selectorParser.Root} selectors
*/
function ignoreNot(selectors) {
selectors.walkPseudos((pseudo) => {
if (pseudo.value === ':not') {
pseudo.remove()
}
})
}
function extractCandidates(node, state = { containsNonOnDemandable: false }, depth = 0) { function extractCandidates(node, state = { containsNonOnDemandable: false }, depth = 0) {
let classes = [] let classes = []
let selectors = []
// Handle normal rules
if (node.type === 'rule') { if (node.type === 'rule') {
// Ignore everything inside a :not(...). This allows you to write code like // Handle normal rules
// `div:not(.foo)`. If `.foo` is never found in your code, then we used to selectors.push(...node.selectors)
// not generated it. But now we will ignore everything inside a `:not`, so } else if (node.type === 'atrule') {
// that it still gets generated. // Handle at-rules (which contains nested rules)
function ignoreNot(selectors) { node.walkRules((rule) => selectors.push(...rule.selectors))
selectors.walkPseudos((pseudo) => {
if (pseudo.value === ':not') {
pseudo.remove()
}
})
}
for (let selector of node.selectors) {
let classCandidates = getClasses(selector, ignoreNot)
// At least one of the selectors contains non-"on-demandable" candidates.
if (classCandidates.length === 0) {
state.containsNonOnDemandable = true
}
for (let classCandidate of classCandidates) {
classes.push(classCandidate)
}
}
} }
// Handle at-rules (which contains nested rules) for (let selector of selectors) {
else if (node.type === 'atrule') { let classCandidates = getClasses(selector, ignoreNot)
node.walkRules((rule) => {
for (let classCandidate of rule.selectors.flatMap((selector) => getClasses(selector))) { // At least one of the selectors contains non-"on-demandable" candidates.
classes.push(classCandidate) if (classCandidates.length === 0) {
} state.containsNonOnDemandable = true
}) }
for (let classCandidate of classCandidates) {
classes.push(classCandidate)
}
} }
if (depth === 0) { if (depth === 0) {
@ -230,8 +233,8 @@ export function parseVariant(variant) {
return ({ format }) => format(str) return ({ format }) => format(str)
} }
let [, name, params] = /@(.*?)( .+|[({].*)/g.exec(str) let [, name, params] = /@(\S*)( .+|[({].*)?/g.exec(str)
return ({ wrap }) => wrap(postcss.atRule({ name, params: params.trim() })) return ({ wrap }) => wrap(postcss.atRule({ name, params: params?.trim() ?? '' }))
}) })
.reverse() .reverse()
@ -434,10 +437,8 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
}, },
} }
let modifiersEnabled = flagEnabled(tailwindConfig, 'generalizedModifiers')
let ruleSets = [] let ruleSets = []
.concat(modifiersEnabled ? rule(value, extras) : rule(value)) .concat(rule(value, extras))
.filter(Boolean) .filter(Boolean)
.map((declaration) => ({ .map((declaration) => ({
[nameClass(identifier, modifier)]: declaration, [nameClass(identifier, modifier)]: declaration,
@ -514,10 +515,8 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
}, },
} }
let modifiersEnabled = flagEnabled(tailwindConfig, 'generalizedModifiers')
let ruleSets = [] let ruleSets = []
.concat(modifiersEnabled ? rule(value, extras) : rule(value)) .concat(rule(value, extras))
.filter(Boolean) .filter(Boolean)
.map((declaration) => ({ .map((declaration) => ({
[nameClass(identifier, modifier)]: declaration, [nameClass(identifier, modifier)]: declaration,
@ -585,18 +584,13 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
let id = options?.id ?? ++variantIdentifier let id = options?.id ?? ++variantIdentifier
let isSpecial = variant === '@' let isSpecial = variant === '@'
let modifiersEnabled = flagEnabled(tailwindConfig, 'generalizedModifiers')
for (let [key, value] of Object.entries(options?.values ?? {})) { for (let [key, value] of Object.entries(options?.values ?? {})) {
if (key === 'DEFAULT') continue if (key === 'DEFAULT') continue
api.addVariant( api.addVariant(
isSpecial ? `${variant}${key}` : `${variant}-${key}`, isSpecial ? `${variant}${key}` : `${variant}-${key}`,
({ args, container }) => { ({ args, container }) => {
return variantFn( return variantFn(value, { modifier: args?.modifier, container })
value,
modifiersEnabled ? { modifier: args?.modifier, container } : { container }
)
}, },
{ {
@ -624,7 +618,7 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
: // Falling back to args if it is a string, otherwise '' for older intellisense : // Falling back to args if it is a string, otherwise '' for older intellisense
// (JetBrains) plugins. // (JetBrains) plugins.
args?.value ?? (typeof args === 'string' ? args : ''), args?.value ?? (typeof args === 'string' ? args : ''),
modifiersEnabled ? { modifier: args?.modifier, container } : { container } { modifier: args?.modifier, container }
) )
}, },
{ {
@ -729,7 +723,7 @@ function collectLayerPlugins(root) {
} }
function resolvePlugins(context, root) { function resolvePlugins(context, root) {
let corePluginList = Object.entries({ ...variantPlugins, ...corePlugins }) let corePluginList = Object.entries(corePlugins)
.map(([name, plugin]) => { .map(([name, plugin]) => {
if (!context.tailwindConfig.corePlugins.includes(name)) { if (!context.tailwindConfig.corePlugins.includes(name)) {
return null return null
@ -754,6 +748,7 @@ function resolvePlugins(context, root) {
let beforeVariants = [ let beforeVariants = [
variantPlugins['pseudoElementVariants'], variantPlugins['pseudoElementVariants'],
variantPlugins['pseudoClassVariants'], variantPlugins['pseudoClassVariants'],
variantPlugins['hasVariants'],
variantPlugins['ariaVariants'], variantPlugins['ariaVariants'],
variantPlugins['dataVariants'], variantPlugins['dataVariants'],
] ]
@ -762,6 +757,7 @@ function resolvePlugins(context, root) {
variantPlugins['directionVariants'], variantPlugins['directionVariants'],
variantPlugins['reducedMotionVariants'], variantPlugins['reducedMotionVariants'],
variantPlugins['prefersContrastVariants'], variantPlugins['prefersContrastVariants'],
variantPlugins['forcedColorsVariants'],
variantPlugins['darkVariants'], variantPlugins['darkVariants'],
variantPlugins['printVariant'], variantPlugins['printVariant'],
variantPlugins['screenVariants'], variantPlugins['screenVariants'],
@ -943,13 +939,17 @@ function registerPlugins(plugins, context) {
// Sort all classes in order // Sort all classes in order
// Non-tailwind classes won't be generated and will be left as `null` // Non-tailwind classes won't be generated and will be left as `null`
let rules = generateRules(new Set(sorted), context) let rules = generateRules(new Set(sorted), context, true)
rules = context.offsets.sort(rules) rules = context.offsets.sort(rules)
let idx = BigInt(parasiteUtilities.length) let idx = BigInt(parasiteUtilities.length)
for (const [, rule] of rules) { for (const [, rule] of rules) {
sortedClassNames.set(rule.raws.tailwind.candidate, idx++) let candidate = rule.raws.tailwind.candidate
// When multiple rules match a candidate
// always take the position of the first one
sortedClassNames.set(candidate, sortedClassNames.get(candidate) ?? idx++)
} }
return classes.map((className) => { return classes.map((className) => {
@ -1119,17 +1119,24 @@ function registerPlugins(plugins, context) {
} }
let isArbitraryVariant = !(value in (options.values ?? {})) let isArbitraryVariant = !(value in (options.values ?? {}))
let internalFeatures = options[INTERNAL_FEATURES] ?? {}
let respectPrefix = (() => {
if (isArbitraryVariant) return false
if (internalFeatures.respectPrefix === false) return false
return true
})()
formatStrings = formatStrings.map((format) => formatStrings = formatStrings.map((format) =>
format.map((str) => ({ format.map((str) => ({
format: str, format: str,
isArbitraryVariant, respectPrefix,
})) }))
) )
manualFormatStrings = manualFormatStrings.map((format) => ({ manualFormatStrings = manualFormatStrings.map((format) => ({
format, format,
isArbitraryVariant, respectPrefix,
})) }))
let opts = { let opts = {

View file

@ -63,9 +63,7 @@ function getTailwindConfig(configOrPath) {
} }
// It's a plain object, not a path // It's a plain object, not a path
let newConfig = resolveConfig( let newConfig = resolveConfig(configOrPath?.config ?? configOrPath ?? {})
configOrPath.config === undefined ? configOrPath : configOrPath.config
)
newConfig = validateConfig(newConfig) newConfig = validateConfig(newConfig)

View file

@ -1,16 +1,12 @@
import pkg from '../../package.json'
export const env = export const env =
typeof process !== 'undefined' typeof process !== 'undefined'
? { ? {
NODE_ENV: process.env.NODE_ENV, NODE_ENV: process.env.NODE_ENV,
DEBUG: resolveDebug(process.env.DEBUG), DEBUG: resolveDebug(process.env.DEBUG),
ENGINE: pkg.tailwindcss.engine,
} }
: { : {
NODE_ENV: 'production', NODE_ENV: 'production',
DEBUG: false, DEBUG: false,
ENGINE: pkg.tailwindcss.engine,
} }
export const contextMap = new Map() export const contextMap = new Map()

View file

@ -1 +0,0 @@
import './cli/index'

View file

@ -1,91 +0,0 @@
import packageJson from '../../../../package.json'
import browserslist from 'browserslist'
import { Result } from 'postcss'
import {
// @ts-ignore
lazyPostcss,
// @ts-ignore
lazyPostcssImport,
// @ts-ignore
lazyCssnano,
// @ts-ignore
} from '../../../../peers/index'
export function lazyLightningCss() {
// TODO: Make this lazy/bundled
return require('lightningcss')
}
let lightningCss
function loadLightningCss() {
if (lightningCss) {
return lightningCss
}
// Try to load a local version first
try {
return (lightningCss = require('lightningcss'))
} catch {}
return (lightningCss = lazyLightningCss())
}
export async function lightningcss(shouldMinify: boolean, result: Result) {
let css = loadLightningCss()
try {
let transformed = css.transform({
filename: result.opts.from || 'input.css',
code: Buffer.from(result.css, 'utf-8'),
minify: shouldMinify,
sourceMap: !!result.map,
inputSourceMap: result.map ? result.map.toString() : undefined,
targets: css.browserslistToTargets(browserslist(packageJson.browserslist)),
drafts: {
nesting: true,
},
})
return Object.assign(result, {
css: transformed.code.toString('utf8'),
map: result.map
? Object.assign(result.map, {
toString() {
return transformed.map.toString()
},
})
: result.map,
})
} catch (err) {
console.error('Unable to use Lightning CSS. Using raw version instead.')
console.error(err)
return result
}
}
/**
* @returns {import('postcss')}
*/
export function loadPostcss() {
// Try to load a local `postcss` version first
try {
return require('postcss')
} catch {}
return lazyPostcss()
}
export function loadPostcssImport() {
// Try to load a local `postcss-import` version first
try {
return require('postcss-import')
} catch {}
return lazyPostcssImport()
}

View file

@ -1,47 +0,0 @@
import fs from 'fs'
import path from 'path'
import { resolveDefaultConfigPath } from '../../../util/resolveConfigPath'
import { createProcessor } from './plugin'
export async function build(args) {
let input = args['--input']
let shouldWatch = args['--watch']
// TODO: Deprecate this in future versions
if (!input && args['_'][1]) {
console.error('[deprecation] Running tailwindcss without -i, please provide an input file.')
input = args['--input'] = args['_'][1]
}
if (input && input !== '-' && !fs.existsSync((input = path.resolve(input)))) {
console.error(`Specified input file ${args['--input']} does not exist.`)
process.exit(9)
}
if (args['--config'] && !fs.existsSync((args['--config'] = path.resolve(args['--config'])))) {
console.error(`Specified config file ${args['--config']} does not exist.`)
process.exit(9)
}
// TODO: Reference the @config path here if exists
let configPath = args['--config'] ? args['--config'] : resolveDefaultConfigPath()
let processor = await createProcessor(args, configPath)
if (shouldWatch) {
// Abort the watcher if stdin is closed to avoid zombie processes
// You can disable this behavior with --watch=always
if (args['--watch'] !== 'always') {
process.stdin.on('end', () => process.exit(0))
}
process.stdin.resume()
await processor.watch()
} else {
await processor.build().catch((e) => {
console.error(e)
process.exit(1)
})
}
}

View file

@ -1,442 +0,0 @@
import path from 'path'
import fs from 'fs'
import postcssrc from 'postcss-load-config'
import { lilconfig } from 'lilconfig'
import loadPlugins from 'postcss-load-config/src/plugins' // Little bit scary, looking at private/internal API
import loadOptions from 'postcss-load-config/src/options' // Little bit scary, looking at private/internal API
import tailwind from '../../../processTailwindFeatures'
import { loadPostcss, loadPostcssImport, lightningcss } from './deps'
import { formatNodes, drainStdin, outputFile } from './utils'
import { env } from '../../../lib/sharedState'
import resolveConfig from '../../../../resolveConfig'
import { parseCandidateFiles } from '../../../lib/content'
import { createWatcher } from './watching'
import fastGlob from 'fast-glob'
import { findAtConfigPath } from '../../../lib/findAtConfigPath'
import log from '../../../util/log'
import { loadConfig } from '../../../lib/load-config'
import getModuleDependencies from '../../../lib/getModuleDependencies'
import type { Config } from '../../../../types'
/**
*
* @param {string} [customPostCssPath ]
* @returns
*/
async function loadPostCssPlugins(customPostCssPath) {
let config = customPostCssPath
? await (async () => {
let file = path.resolve(customPostCssPath)
// Implementation, see: https://unpkg.com/browse/postcss-load-config@3.1.0/src/index.js
// @ts-ignore
let { config = {} } = await lilconfig('postcss').load(file)
if (typeof config === 'function') {
config = config()
} else {
config = Object.assign({}, config)
}
if (!config.plugins) {
config.plugins = []
}
return {
file,
plugins: loadPlugins(config, file),
options: loadOptions(config, file),
}
})()
: await postcssrc()
let configPlugins = config.plugins
let configPluginTailwindIdx = configPlugins.findIndex((plugin) => {
if (typeof plugin === 'function' && plugin.name === 'tailwindcss') {
return true
}
if (typeof plugin === 'object' && plugin !== null && plugin.postcssPlugin === 'tailwindcss') {
return true
}
return false
})
let beforePlugins =
configPluginTailwindIdx === -1 ? [] : configPlugins.slice(0, configPluginTailwindIdx)
let afterPlugins =
configPluginTailwindIdx === -1
? configPlugins
: configPlugins.slice(configPluginTailwindIdx + 1)
return [beforePlugins, afterPlugins, config.options]
}
function loadBuiltinPostcssPlugins() {
let postcss = loadPostcss()
let IMPORT_COMMENT = '__TAILWIND_RESTORE_IMPORT__: '
return [
[
(root) => {
root.walkAtRules('import', (rule) => {
if (rule.params.slice(1).startsWith('tailwindcss/')) {
rule.after(postcss.comment({ text: IMPORT_COMMENT + rule.params }))
rule.remove()
}
})
},
loadPostcssImport(),
(root) => {
root.walkComments((rule) => {
if (rule.text.startsWith(IMPORT_COMMENT)) {
rule.after(
postcss.atRule({
name: 'import',
params: rule.text.replace(IMPORT_COMMENT, ''),
})
)
rule.remove()
}
})
},
],
[],
{},
]
}
let state = {
/** @type {any} */
context: null,
/** @type {ReturnType<typeof createWatcher> | null} */
watcher: null,
/** @type {{content: string, extension: string}[]} */
changedContent: [],
/** @type {{config: Config, dependencies: Set<string>, dispose: Function } | null} */
configBag: null,
contextDependencies: new Set(),
/** @type {import('../../lib/content.js').ContentPath[]} */
contentPaths: [],
refreshContentPaths() {
this.contentPaths = parseCandidateFiles(this.context, this.context?.tailwindConfig)
},
get config() {
return this.context.tailwindConfig
},
get contentPatterns() {
return {
all: this.contentPaths.map((contentPath) => contentPath.pattern),
dynamic: this.contentPaths
.filter((contentPath) => contentPath.glob !== undefined)
.map((contentPath) => contentPath.pattern),
}
},
loadConfig(configPath, content) {
if (this.watcher && configPath) {
this.refreshConfigDependencies()
}
let config = loadConfig(configPath)
let dependencies = getModuleDependencies(configPath)
this.configBag = {
config,
dependencies,
dispose() {
for (let file of dependencies) {
delete require.cache[require.resolve(file)]
}
},
}
// @ts-ignore
this.configBag.config = resolveConfig(this.configBag.config, { content: { files: [] } })
// Override content files if `--content` has been passed explicitly
if (content?.length > 0) {
this.configBag.config.content.files = content
}
return this.configBag.config
},
refreshConfigDependencies(configPath) {
env.DEBUG && console.time('Module dependencies')
this.configBag?.dispose()
env.DEBUG && console.timeEnd('Module dependencies')
},
readContentPaths() {
let content = []
// Resolve globs from the content config
// TODO: When we make the postcss plugin async-capable this can become async
let files = fastGlob.sync(this.contentPatterns.all)
for (let file of files) {
if (__OXIDE__) {
content.push({
file,
extension: path.extname(file).slice(1),
})
} else {
content.push({
content: fs.readFileSync(path.resolve(file), 'utf8'),
extension: path.extname(file).slice(1),
})
}
}
// Resolve raw content in the tailwind config
let rawContent = this.config.content.files.filter((file) => {
return file !== null && typeof file === 'object'
})
for (let { raw: htmlContent, extension = 'html' } of rawContent) {
content.push({ content: htmlContent, extension })
}
return content
},
getContext({ createContext, cliConfigPath, root, result, content }) {
if (this.context) {
this.context.changedContent = this.changedContent.splice(0)
return this.context
}
env.DEBUG && console.time('Searching for config')
let configPath = findAtConfigPath(root, result) ?? cliConfigPath
env.DEBUG && console.timeEnd('Searching for config')
env.DEBUG && console.time('Loading config')
let config = this.loadConfig(configPath, content)
env.DEBUG && console.timeEnd('Loading config')
env.DEBUG && console.time('Creating context')
this.context = createContext(config, [])
Object.assign(this.context, {
userConfigPath: configPath,
})
env.DEBUG && console.timeEnd('Creating context')
env.DEBUG && console.time('Resolving content paths')
this.refreshContentPaths()
env.DEBUG && console.timeEnd('Resolving content paths')
if (this.watcher) {
env.DEBUG && console.time('Watch new files')
this.watcher.refreshWatchedFiles()
env.DEBUG && console.timeEnd('Watch new files')
}
for (let file of this.readContentPaths()) {
this.context.changedContent.push(file)
}
return this.context
},
}
export async function createProcessor(args, cliConfigPath) {
let postcss = loadPostcss()
let input = args['--input']
let output = args['--output']
let includePostCss = args['--postcss']
let customPostCssPath = typeof args['--postcss'] === 'string' ? args['--postcss'] : undefined
let [beforePlugins, afterPlugins, postcssOptions] = includePostCss
? await loadPostCssPlugins(customPostCssPath)
: loadBuiltinPostcssPlugins()
if (args['--purge']) {
log.warn('purge-flag-deprecated', [
'The `--purge` flag has been deprecated.',
'Please use `--content` instead.',
])
if (!args['--content']) {
args['--content'] = args['--purge']
}
}
let content = args['--content']?.split(/(?<!{[^}]+),/) ?? []
let tailwindPlugin = () => {
return {
postcssPlugin: 'tailwindcss',
Once(root, { result }) {
env.DEBUG && console.time('Compiling CSS')
tailwind(({ createContext }) => {
console.error()
console.error('Rebuilding...')
return () => {
return state.getContext({
createContext,
cliConfigPath,
root,
result,
content,
})
}
})(root, result)
env.DEBUG && console.timeEnd('Compiling CSS')
},
}
}
tailwindPlugin.postcss = true
let plugins = [
...beforePlugins,
tailwindPlugin,
!args['--minify'] && formatNodes,
...afterPlugins,
].filter(Boolean)
/** @type {import('postcss').Processor} */
// @ts-ignore
let processor = postcss(plugins)
async function readInput() {
// Piping in data, let's drain the stdin
if (input === '-') {
return drainStdin()
}
// Input file has been provided
if (input) {
return fs.promises.readFile(path.resolve(input), 'utf8')
}
// No input file provided, fallback to default atrules
return '@tailwind base; @tailwind components; @tailwind utilities'
}
async function build() {
let start = process.hrtime.bigint()
return readInput()
.then((css) => processor.process(css, { ...postcssOptions, from: input, to: output }))
.then((result) => lightningcss(!!args['--minify'], result))
.then((result) => {
if (!state.watcher) {
return result
}
env.DEBUG && console.time('Recording PostCSS dependencies')
for (let message of result.messages) {
if (message.type === 'dependency') {
state.contextDependencies.add(message.file)
}
}
env.DEBUG && console.timeEnd('Recording PostCSS dependencies')
// TODO: This needs to be in a different spot
env.DEBUG && console.time('Watch new files')
state.watcher.refreshWatchedFiles()
env.DEBUG && console.timeEnd('Watch new files')
return result
})
.then((result) => {
if (!output) {
process.stdout.write(result.css)
return
}
return Promise.all([
outputFile(result.opts.to, result.css),
result.map && outputFile(result.opts.to + '.map', result.map.toString()),
])
})
.then(() => {
let end = process.hrtime.bigint()
console.error()
console.error('Done in', (end - start) / BigInt(1e6) + 'ms.')
})
.then(
() => {},
(err) => {
// TODO: If an initial build fails we can't easily pick up any PostCSS dependencies
// that were collected before the error occurred
// The result is not stored on the error so we have to store it externally
// and pull the messages off of it here somehow
// This results in a less than ideal DX because the watcher will not pick up
// changes to imported CSS if one of them caused an error during the initial build
// If you fix it and then save the main CSS file so there's no error
// The watcher will start watching the imported CSS files and will be
// resilient to future errors.
if (state.watcher) {
console.error(err)
} else {
return Promise.reject(err)
}
}
)
}
/**
* @param {{file: string, content(): Promise<string>, extension: string}[]} changes
*/
async function parseChanges(changes) {
return Promise.all(
changes.map(async (change) => ({
content: await change.content(),
extension: change.extension,
}))
)
}
if (input !== undefined && input !== '-') {
state.contextDependencies.add(path.resolve(input))
}
return {
build,
watch: async () => {
state.watcher = createWatcher(args, {
state,
/**
* @param {{file: string, content(): Promise<string>, extension: string}[]} changes
*/
async rebuild(changes) {
let needsNewContext = changes.some((change) => {
return (
state.configBag?.dependencies.has(change.file) ||
state.contextDependencies.has(change.file)
)
})
if (needsNewContext) {
state.context = null
} else {
for (let change of await parseChanges(changes)) {
state.changedContent.push(change)
}
}
return build()
},
})
await build()
},
}
}

View file

@ -1,74 +0,0 @@
import fs from 'fs'
import path from 'path'
export function indentRecursive(node, indent = 0) {
node.each &&
node.each((child, i) => {
if (!child.raws.before || !child.raws.before.trim() || child.raws.before.includes('\n')) {
child.raws.before = `\n${node.type !== 'rule' && i > 0 ? '\n' : ''}${' '.repeat(indent)}`
}
child.raws.after = `\n${' '.repeat(indent)}`
indentRecursive(child, indent + 1)
})
}
export function formatNodes(root) {
indentRecursive(root)
if (root.first) {
root.first.raws.before = ''
}
}
/**
* When rapidly saving files atomically a couple of situations can happen:
* - The file is missing since the external program has deleted it by the time we've gotten around to reading it from the earlier save.
* - The file is being written to by the external program by the time we're going to read it and is thus treated as busy because a lock is held.
*
* To work around this we retry reading the file a handful of times with a delay between each attempt
*
* @param {string} path
* @param {number} tries
* @returns {Promise<string | undefined>}
* @throws {Error} If the file is still missing or busy after the specified number of tries
*/
export async function readFileWithRetries(path, tries = 5) {
for (let n = 0; n <= tries; n++) {
try {
return await fs.promises.readFile(path, 'utf8')
} catch (err) {
if (n !== tries) {
if (err.code === 'ENOENT' || err.code === 'EBUSY') {
await new Promise((resolve) => setTimeout(resolve, 10))
continue
}
}
throw err
}
}
}
export function drainStdin() {
return new Promise((resolve, reject) => {
let result = ''
process.stdin.on('data', (chunk) => {
result += chunk
})
process.stdin.on('end', () => resolve(result))
process.stdin.on('error', (err) => reject(err))
})
}
export async function outputFile(file, newContents) {
try {
let currentContents = await fs.promises.readFile(file, 'utf8')
if (currentContents === newContents) {
return // Skip writing the file
}
} catch {}
// Write the file
await fs.promises.mkdir(path.dirname(file), { recursive: true })
await fs.promises.writeFile(file, newContents, 'utf8')
}

Some files were not shown because too many files have changed in this diff Show more