canopy-webpack-config 4.4.5 → 4.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -4
- package/package.json +9 -2
- package/src/canopy-webpack-config-esm.mjs +11 -1
- package/src/canopy-webpack-config.js +11 -1
- package/src/tailwind/base.css +18 -0
- package/src/tailwind/index.js +250 -0
package/README.md
CHANGED
|
@@ -1,19 +1,24 @@
|
|
|
1
1
|
# canopy-webpack-config
|
|
2
|
+
|
|
2
3
|
Some defaults for webpack configs at Canopy.
|
|
3
4
|
|
|
4
5
|
## Goals
|
|
6
|
+
|
|
5
7
|
- Ensure webpack externals for common dependencies are correct. This ensures smaller bundle sizes.
|
|
6
8
|
- Provide some defaults for people implementing sofe services.
|
|
7
9
|
- Still be 100% overrideable.
|
|
8
10
|
|
|
9
11
|
## Things we don't want
|
|
12
|
+
|
|
10
13
|
- Hiding webpack configs at a level comparable to create-react-app or angular-cli. Nothing will need to be "ejected," you can always override anything you want.
|
|
11
14
|
- Discouraging people from experimenting with their webpack config.
|
|
12
15
|
- Discouraging people from knowing webpack.
|
|
13
16
|
- Being overly opininated about how you do things like css (or other "autonomy" areas of building a sofe service).
|
|
14
17
|
|
|
15
18
|
## Usage
|
|
19
|
+
|
|
16
20
|
First, install the library as a devDependency.
|
|
21
|
+
|
|
17
22
|
```bash
|
|
18
23
|
yarn add --dev canopy-webpack-config @babel/runtime @babel/plugin-transform-runtime
|
|
19
24
|
```
|
|
@@ -24,17 +29,17 @@ file, call the `canopyWebpackConfig()` function to get some defaults and add/ove
|
|
|
24
29
|
```js
|
|
25
30
|
// webpack.config.js
|
|
26
31
|
|
|
27
|
-
const canopyWebpackConfig = require(
|
|
32
|
+
const canopyWebpackConfig = require("canopy-webpack-config");
|
|
28
33
|
|
|
29
|
-
module.exports = canopyWebpackConfig(
|
|
34
|
+
module.exports = canopyWebpackConfig("login-ui", {
|
|
30
35
|
// Override or add anything you want to your webpack config
|
|
31
36
|
module: {
|
|
32
37
|
rules: [
|
|
33
38
|
// e.g. apply a css loader if using css-modules
|
|
34
|
-
{ loader:
|
|
39
|
+
{ loader: "css-loader" },
|
|
35
40
|
],
|
|
36
41
|
},
|
|
37
|
-
})
|
|
42
|
+
});
|
|
38
43
|
```
|
|
39
44
|
|
|
40
45
|
Finally, create the following `scripts` in your package.json:
|
|
@@ -55,6 +60,7 @@ Now add `yarn build` to your `.gitlab-ci.yml` file for the build step. You can r
|
|
|
55
60
|
will start up a web server that is ready to go as a sofe override.
|
|
56
61
|
|
|
57
62
|
## API
|
|
63
|
+
|
|
58
64
|
- `canopyWebpackConfig(name, config)`: The default export of the canopy-webpack-config npm library. This function requires both of its arguments.
|
|
59
65
|
The first argument is a string name for the library you are exporting. The second is a webpack config that will be merged with the defaults that
|
|
60
66
|
canopy-webpack-config provides. The library will put the bundled files into the "build" directory. Note that this project assumes that you use
|
|
@@ -67,10 +73,13 @@ will start up a web server that is ready to go as a sofe override.
|
|
|
67
73
|
needing to do anything.
|
|
68
74
|
|
|
69
75
|
## Debugging
|
|
76
|
+
|
|
70
77
|
To see your full webpack config, simply add a `--env.debug` to your webpack cli command.
|
|
71
78
|
|
|
72
79
|
## Features and assumptions
|
|
80
|
+
|
|
73
81
|
canopy-webpack-config assumes a few things about your project and provides defaults for those things:
|
|
82
|
+
|
|
74
83
|
- It automatically uses babel-loader to compile js files.
|
|
75
84
|
- It compiles your library to AMD format
|
|
76
85
|
- It compiles your code into the `build` directory relative to where you started the webpack process.
|
|
@@ -78,20 +87,49 @@ canopy-webpack-config assumes a few things about your project and provides defau
|
|
|
78
87
|
- Requires webpack-dev-server >= 3.4.0
|
|
79
88
|
|
|
80
89
|
## Limitations
|
|
90
|
+
|
|
81
91
|
the webpack config for canopy-webpack-config will always create the output bundle in the directory that the webpack process was started in. This
|
|
82
92
|
is different than how webpack configs normally work -- they usually create the output bundle in relation to the directory in which the webpack config
|
|
83
93
|
file is placed.
|
|
84
94
|
|
|
85
95
|
## Optional modifiers
|
|
96
|
+
|
|
86
97
|
We support a few config options for builds, including typescript and an externals submodule.
|
|
98
|
+
|
|
87
99
|
```
|
|
88
100
|
module.exports = canopyWebpackConfig('login-ui', {}, {
|
|
89
101
|
typescript: true,
|
|
90
102
|
externals: true
|
|
91
103
|
})
|
|
92
104
|
```
|
|
105
|
+
|
|
93
106
|
### Typescript `<bool>`
|
|
107
|
+
|
|
94
108
|
Pass an options object with `{typescript: true}` when your service uss typescript.
|
|
95
109
|
|
|
96
110
|
### Externals `<bool>`
|
|
111
|
+
|
|
97
112
|
Pass an options object with `{externals: true}` when you are looking to export a submodule from `/src/externals.{js|ts}`. This should be for exports that must be imported synchronously such as hooks or queries that do not have other internal dependencies (to avoid circular dependencies).
|
|
113
|
+
|
|
114
|
+
### Tailwind `<object>`
|
|
115
|
+
|
|
116
|
+
Pass `{ tailwind: { prefix: '<service_prefix>' } }` and import the stylesheet in your app entry:
|
|
117
|
+
|
|
118
|
+
Use https://github.com/CanopyTax/frontend-docs/wiki/Repo-abbreviations to get the correct prefix for the service.
|
|
119
|
+
|
|
120
|
+
```js
|
|
121
|
+
// webpack.config.js
|
|
122
|
+
module.exports = canopyWebpackConfig(
|
|
123
|
+
"<service_name>",
|
|
124
|
+
{},
|
|
125
|
+
{
|
|
126
|
+
typescript: true,
|
|
127
|
+
tailwind: { prefix: "ru" },
|
|
128
|
+
},
|
|
129
|
+
);
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
```js
|
|
133
|
+
// src/<service_name>.ts
|
|
134
|
+
import "canopy-webpack-config/tailwind.css";
|
|
135
|
+
```
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "canopy-webpack-config",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.5.0",
|
|
4
4
|
"description": "Some defaults for webpack configs at canopy",
|
|
5
5
|
"main": "src/canopy-webpack-config.js",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": "./src/canopy-webpack-config.js",
|
|
8
|
-
"./esm": "./src/canopy-webpack-config-esm.mjs"
|
|
8
|
+
"./esm": "./src/canopy-webpack-config-esm.mjs",
|
|
9
|
+
"./tailwind.css": "./src/tailwind/base.css"
|
|
9
10
|
},
|
|
10
11
|
"repository": "git@github.com:CanopyTax/canopy-webpack-config.git",
|
|
11
12
|
"license": "Apache-2.0",
|
|
@@ -17,7 +18,13 @@
|
|
|
17
18
|
"prettier": "^3.6.2"
|
|
18
19
|
},
|
|
19
20
|
"dependencies": {
|
|
21
|
+
"@tailwindcss/postcss": "4.3.3",
|
|
20
22
|
"clean-webpack-plugin": "^4.0.0",
|
|
23
|
+
"css-loader": "^7.1.2",
|
|
24
|
+
"postcss": "^8.4.49",
|
|
25
|
+
"postcss-loader": "^8.1.1",
|
|
26
|
+
"style-loader": "^4.0.0",
|
|
27
|
+
"tailwindcss": "4.3.3",
|
|
21
28
|
"url": "^0.11.0",
|
|
22
29
|
"webpack-bundle-analyzer": "^4.7.0",
|
|
23
30
|
"webpack-merge": "^4.2.1"
|
|
@@ -4,6 +4,7 @@ import { BundleAnalyzerPlugin } from "webpack-bundle-analyzer";
|
|
|
4
4
|
import merge from "webpack-merge";
|
|
5
5
|
import fs from "fs";
|
|
6
6
|
import os from "os";
|
|
7
|
+
import { tailwindSupport } from "./tailwind/index.js";
|
|
7
8
|
|
|
8
9
|
const homedir = os.homedir();
|
|
9
10
|
|
|
@@ -49,7 +50,7 @@ const externalPatterns = [
|
|
|
49
50
|
];
|
|
50
51
|
|
|
51
52
|
export default function (name, overridesConfig = {}, options = {}) {
|
|
52
|
-
const { externals: hasExternals } = options;
|
|
53
|
+
const { externals: hasExternals, tailwind } = options;
|
|
53
54
|
|
|
54
55
|
if (typeof name !== "string") {
|
|
55
56
|
throw new Error(
|
|
@@ -66,6 +67,12 @@ export default function (name, overridesConfig = {}, options = {}) {
|
|
|
66
67
|
);
|
|
67
68
|
}
|
|
68
69
|
|
|
70
|
+
const {
|
|
71
|
+
rules: tailwindRules,
|
|
72
|
+
plugins: tailwindPlugins,
|
|
73
|
+
alias: tailwindAlias,
|
|
74
|
+
} = tailwindSupport(tailwind);
|
|
75
|
+
|
|
69
76
|
return function (env = {}) {
|
|
70
77
|
const defaultCanopyConfig = {
|
|
71
78
|
mode: env.dev || isDevServer ? "development" : "production",
|
|
@@ -122,6 +129,7 @@ export default function (name, overridesConfig = {}, options = {}) {
|
|
|
122
129
|
fullySpecified: false,
|
|
123
130
|
extensions: [".tsx", ".ts", ".js", ".jsx", ".json"],
|
|
124
131
|
modules: [process.cwd(), "node_modules"],
|
|
132
|
+
alias: tailwindAlias,
|
|
125
133
|
},
|
|
126
134
|
|
|
127
135
|
module: {
|
|
@@ -131,6 +139,7 @@ export default function (name, overridesConfig = {}, options = {}) {
|
|
|
131
139
|
exclude: /node_modules/,
|
|
132
140
|
use: "babel-loader",
|
|
133
141
|
},
|
|
142
|
+
...tailwindRules,
|
|
134
143
|
],
|
|
135
144
|
},
|
|
136
145
|
|
|
@@ -138,6 +147,7 @@ export default function (name, overridesConfig = {}, options = {}) {
|
|
|
138
147
|
new CleanWebpackPlugin({
|
|
139
148
|
cleanOnceBeforeBuildPatterns: ["**/*", "!.gitkeep"],
|
|
140
149
|
}),
|
|
150
|
+
...tailwindPlugins,
|
|
141
151
|
env.analyze === "server" &&
|
|
142
152
|
new BundleAnalyzerPlugin({ analyzerMode: "server" }),
|
|
143
153
|
env.analyze === "static" &&
|
|
@@ -5,6 +5,7 @@ const BundleAnalyzerPlugin =
|
|
|
5
5
|
const merge = require("webpack-merge");
|
|
6
6
|
const fs = require("fs");
|
|
7
7
|
const homedir = require("os").homedir();
|
|
8
|
+
const { tailwindSupport } = require("./tailwind/index.js");
|
|
8
9
|
|
|
9
10
|
let isDevServer = false;
|
|
10
11
|
|
|
@@ -25,7 +26,7 @@ const port =
|
|
|
25
26
|
: "8080";
|
|
26
27
|
|
|
27
28
|
module.exports = function (name, overridesConfig = {}, options = {}) {
|
|
28
|
-
const { typescript, externals } = options;
|
|
29
|
+
const { typescript, externals, tailwind } = options;
|
|
29
30
|
if (typeof name !== "string") {
|
|
30
31
|
throw new Error(
|
|
31
32
|
"canopy-webpack-config expects a string name as the first argument",
|
|
@@ -42,6 +43,12 @@ module.exports = function (name, overridesConfig = {}, options = {}) {
|
|
|
42
43
|
);
|
|
43
44
|
}
|
|
44
45
|
|
|
46
|
+
const {
|
|
47
|
+
rules: tailwindRules,
|
|
48
|
+
plugins: tailwindPlugins,
|
|
49
|
+
alias: tailwindAlias,
|
|
50
|
+
} = tailwindSupport(tailwind);
|
|
51
|
+
|
|
45
52
|
return function (env) {
|
|
46
53
|
if (!env) {
|
|
47
54
|
env = {};
|
|
@@ -81,6 +88,7 @@ module.exports = function (name, overridesConfig = {}, options = {}) {
|
|
|
81
88
|
],
|
|
82
89
|
},
|
|
83
90
|
},
|
|
91
|
+
...tailwindRules,
|
|
84
92
|
],
|
|
85
93
|
},
|
|
86
94
|
resolve: {
|
|
@@ -90,6 +98,7 @@ module.exports = function (name, overridesConfig = {}, options = {}) {
|
|
|
90
98
|
}
|
|
91
99
|
: {}),
|
|
92
100
|
modules: [process.cwd(), "node_modules"],
|
|
101
|
+
alias: tailwindAlias,
|
|
93
102
|
fallback: {
|
|
94
103
|
url: require.resolve("url/"),
|
|
95
104
|
"react/jsx-runtime": "react/jsx-runtime.js",
|
|
@@ -101,6 +110,7 @@ module.exports = function (name, overridesConfig = {}, options = {}) {
|
|
|
101
110
|
new BundleAnalyzerPlugin({
|
|
102
111
|
analyzerMode: env.analyze || "disabled",
|
|
103
112
|
}),
|
|
113
|
+
...tailwindPlugins,
|
|
104
114
|
],
|
|
105
115
|
devtool: "source-map",
|
|
106
116
|
externals: [
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The shared half of every Canopy app's Tailwind entry.
|
|
3
|
+
*
|
|
4
|
+
* canopy-webpack-config copies this file into a generated per-repo entry, underneath the
|
|
5
|
+
* two prefixed Tailwind `@import` lines (theme + utilities) that only the generator can write.
|
|
6
|
+
*
|
|
7
|
+
* Use only bare package specifiers here. The generated entry lives in the consuming repo's
|
|
8
|
+
* node_modules/.cache, and these imports resolve from there (e.g. @canopytax/understory).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/* Shared Canopy design system tokens */
|
|
12
|
+
@import "@canopytax/understory/tailwind/theme.css";
|
|
13
|
+
|
|
14
|
+
/* 10px base font rem recalculation */
|
|
15
|
+
@plugin "@canopytax/understory/tailwind/base-font-size";
|
|
16
|
+
|
|
17
|
+
/* Dark mode variant (same selector as understory) */
|
|
18
|
+
@custom-variant dark (&:is(:root.dark *, [data-mode="dark"] *));
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
|
|
4
|
+
const PLUGIN_NAME = "CanopyTailwindCandidatesPlugin";
|
|
5
|
+
const CACHE_DIR = "node_modules/.cache/canopy-tailwind";
|
|
6
|
+
// Deliberately not `.css`. Every repo has a `{ test: /\.css$/, include: [node_modules] }`
|
|
7
|
+
// rule, and webpack applies every matching rule, so a generated `.css` in here would pick
|
|
8
|
+
// up a second loader chain alongside ours and ship unprocessed Tailwind directives.
|
|
9
|
+
const ENTRY_FILE = "entry.tailwind";
|
|
10
|
+
const CANDIDATES_FILE = "candidates";
|
|
11
|
+
const BASE_CSS = path.resolve(__dirname, "base.css");
|
|
12
|
+
|
|
13
|
+
// tailwindcss is our dependency, not the consuming repo's, so the entry cannot reach it
|
|
14
|
+
// with a bare specifier — the entry resolves from the repo's node_modules, where it may
|
|
15
|
+
// not be hoisted. An absolute path produces byte-identical output and always resolves.
|
|
16
|
+
const TAILWIND_CSS_DIR = path.dirname(
|
|
17
|
+
require.resolve("tailwindcss/package.json"),
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
// What a consumer writes in their app entry. Aliased to the generated file below.
|
|
21
|
+
const PUBLIC_SPECIFIER = "canopy-webpack-config/tailwind.css";
|
|
22
|
+
|
|
23
|
+
const DEFAULT_SOURCES = ["src/**/*.{js,jsx,ts,tsx}"];
|
|
24
|
+
|
|
25
|
+
// Tailwind's built-in prefix only generates a utility when the prefixed candidate
|
|
26
|
+
// appears literally in a scanned file. Our tw() helper composes the prefix at runtime,
|
|
27
|
+
// so the scanner never sees it. We run Tailwind's own extractor ourselves, prepend the
|
|
28
|
+
// prefix to everything it finds, and hand the result back through @source.
|
|
29
|
+
//
|
|
30
|
+
// oxide is a native binary pinned to an exact version by @tailwindcss/postcss, and the
|
|
31
|
+
// extraction has to match the Tailwind that actually builds the CSS. Both are our
|
|
32
|
+
// dependencies now, so resolving from here is what guarantees they agree.
|
|
33
|
+
function loadScanner() {
|
|
34
|
+
try {
|
|
35
|
+
return require("@tailwindcss/oxide").Scanner;
|
|
36
|
+
} catch (err) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
`${PLUGIN_NAME}: could not load @tailwindcss/oxide. It ships with our own ` +
|
|
39
|
+
`@tailwindcss/postcss, so this is a broken canopy-webpack-config install — on ` +
|
|
40
|
+
`CI more often a missing platform binary than a missing package; see the cause.`,
|
|
41
|
+
{ cause: err },
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function buildEntry(prefix, extendPath) {
|
|
47
|
+
let base;
|
|
48
|
+
try {
|
|
49
|
+
base = fs.readFileSync(BASE_CSS, "utf8");
|
|
50
|
+
} catch (err) {
|
|
51
|
+
throw new Error(
|
|
52
|
+
`${PLUGIN_NAME}: could not read the shared Tailwind base at ${BASE_CSS}. This is ` +
|
|
53
|
+
`a broken canopy-webpack-config install, not a problem with your repo.`,
|
|
54
|
+
{ cause: err },
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return [
|
|
59
|
+
`/* Generated by canopy-webpack-config on every build — edits here are lost.`,
|
|
60
|
+
` * Change the \`tailwind\` option in your webpack config, or src/tailwind/base.css`,
|
|
61
|
+
` * in canopy-webpack-config for boilerplate every app shares. */`,
|
|
62
|
+
``,
|
|
63
|
+
`@import "${TAILWIND_CSS_DIR}/theme.css" prefix(${prefix});`,
|
|
64
|
+
`@import "${TAILWIND_CSS_DIR}/utilities.css" prefix(${prefix});`,
|
|
65
|
+
``,
|
|
66
|
+
base.trim(),
|
|
67
|
+
``,
|
|
68
|
+
// Absolute so it does not depend on where the repo-specific file sits.
|
|
69
|
+
...(extendPath ? [`@import "${extendPath}";`, ``] : []),
|
|
70
|
+
`@source "./${CANDIDATES_FILE}";`,
|
|
71
|
+
``,
|
|
72
|
+
].join("\n");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
class TailwindCandidatesPlugin {
|
|
76
|
+
constructor(options = {}) {
|
|
77
|
+
const {
|
|
78
|
+
prefix,
|
|
79
|
+
context = process.cwd(),
|
|
80
|
+
sources = DEFAULT_SOURCES,
|
|
81
|
+
extend,
|
|
82
|
+
} = options;
|
|
83
|
+
|
|
84
|
+
if (typeof prefix !== "string" || !prefix) {
|
|
85
|
+
throw new Error(
|
|
86
|
+
`${PLUGIN_NAME}: \`prefix\` is required, e.g. { prefix: "ru" }`,
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
this.prefix = prefix;
|
|
91
|
+
this.context = context;
|
|
92
|
+
this.sources = sources;
|
|
93
|
+
this.extendPath = extend ? path.resolve(context, extend) : null;
|
|
94
|
+
this.cacheDir = path.resolve(context, CACHE_DIR);
|
|
95
|
+
this.entryPath = path.join(this.cacheDir, ENTRY_FILE);
|
|
96
|
+
this.candidatesPath = path.join(this.cacheDir, CANDIDATES_FILE);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Rewriting an unchanged file would invalidate the Tailwind entry on every compile and
|
|
100
|
+
// loop the dev server.
|
|
101
|
+
writeIfChanged(filePath, contents) {
|
|
102
|
+
let previous;
|
|
103
|
+
try {
|
|
104
|
+
previous = fs.readFileSync(filePath, "utf8");
|
|
105
|
+
} catch (err) {
|
|
106
|
+
if (err && err.code === "ENOENT") {
|
|
107
|
+
previous = null;
|
|
108
|
+
} else {
|
|
109
|
+
throw err;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (previous === contents) return false;
|
|
114
|
+
|
|
115
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
116
|
+
fs.writeFileSync(filePath, contents);
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
generate() {
|
|
121
|
+
if (this.extendPath && !fs.existsSync(this.extendPath)) {
|
|
122
|
+
throw new Error(
|
|
123
|
+
`${PLUGIN_NAME}: \`tailwind.extend\` points at ${this.extendPath}, which does ` +
|
|
124
|
+
`not exist.`,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const Scanner = loadScanner();
|
|
129
|
+
const scanner = new Scanner({
|
|
130
|
+
sources: this.sources.map((pattern) => ({
|
|
131
|
+
base: this.context,
|
|
132
|
+
pattern,
|
|
133
|
+
negated: false,
|
|
134
|
+
})),
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const candidates = scanner.scan();
|
|
138
|
+
|
|
139
|
+
if (candidates.length === 0) {
|
|
140
|
+
throw new Error(
|
|
141
|
+
`${PLUGIN_NAME}: found no Tailwind candidates under ${this.sources.join(", ")}. ` +
|
|
142
|
+
`Building it would produce an empty stylesheet.`,
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Everything the extractor found gets the prefix
|
|
147
|
+
const contents =
|
|
148
|
+
candidates.map((c) => `${this.prefix}:${c}`).join("\n") + "\n";
|
|
149
|
+
|
|
150
|
+
const changed = this.writeIfChanged(this.candidatesPath, contents);
|
|
151
|
+
this.writeIfChanged(
|
|
152
|
+
this.entryPath,
|
|
153
|
+
buildEntry(this.prefix, this.extendPath),
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
return { count: candidates.length, files: scanner.files, changed };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
apply(compiler) {
|
|
160
|
+
// beforeCompile lands before any module is resolved or built, so both generated
|
|
161
|
+
// files are on disk by the time webpack resolves the alias and postcss-loader reads the entry
|
|
162
|
+
compiler.hooks.beforeCompile.tap(PLUGIN_NAME, () => {
|
|
163
|
+
this.lastError = null;
|
|
164
|
+
try {
|
|
165
|
+
this.lastRun = this.generate();
|
|
166
|
+
} catch (err) {
|
|
167
|
+
this.lastError = err;
|
|
168
|
+
if (!compiler.watchMode) throw err;
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
compiler.hooks.afterCompile.tap(PLUGIN_NAME, (compilation) => {
|
|
173
|
+
if (this.lastError) {
|
|
174
|
+
compilation.errors.push(this.lastError);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const run = this.lastRun;
|
|
179
|
+
if (!run) return;
|
|
180
|
+
|
|
181
|
+
// @tailwindcss/postcss reports the candidates file as a dependency and
|
|
182
|
+
// postcss-loader forwards it, so edits to a file already in the graph rebuild on
|
|
183
|
+
// their own. Registering the scanned files and their roots covers additions and
|
|
184
|
+
// deletions.
|
|
185
|
+
for (const file of run.files) {
|
|
186
|
+
compilation.fileDependencies.add(file);
|
|
187
|
+
}
|
|
188
|
+
for (const pattern of this.sources) {
|
|
189
|
+
compilation.contextDependencies.add(
|
|
190
|
+
path.resolve(this.context, pattern.split("*")[0]),
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
if (this.extendPath) {
|
|
194
|
+
compilation.fileDependencies.add(this.extendPath);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
compilation
|
|
198
|
+
.getLogger(PLUGIN_NAME)
|
|
199
|
+
.info(
|
|
200
|
+
`${run.changed ? "wrote" : "reused"} ${run.count} prefixed candidates ` +
|
|
201
|
+
`(${this.prefix}:) from ${run.files.length} files`,
|
|
202
|
+
);
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function tailwindRule(entryPath) {
|
|
208
|
+
return {
|
|
209
|
+
test: (resource) => resource === entryPath,
|
|
210
|
+
use: [
|
|
211
|
+
{ loader: require.resolve("style-loader") },
|
|
212
|
+
{ loader: require.resolve("css-loader") },
|
|
213
|
+
{
|
|
214
|
+
loader: require.resolve("postcss-loader"),
|
|
215
|
+
options: {
|
|
216
|
+
postcssOptions: { plugins: [require("@tailwindcss/postcss")] },
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
],
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Returns the rule, plugin and alias a consuming config needs, or empty ones when the
|
|
224
|
+
// repo has not opted in.
|
|
225
|
+
function tailwindSupport(tailwind) {
|
|
226
|
+
if (!tailwind) {
|
|
227
|
+
return { rules: [], plugins: [], alias: {} };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const plugin = new TailwindCandidatesPlugin(tailwind);
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
rules: [tailwindRule(plugin.entryPath)],
|
|
234
|
+
plugins: [plugin],
|
|
235
|
+
// `$` makes this an exact match, so it only ever catches the stylesheet import.
|
|
236
|
+
alias: { [`${PUBLIC_SPECIFIER}$`]: plugin.entryPath },
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
module.exports = {
|
|
241
|
+
TailwindCandidatesPlugin,
|
|
242
|
+
tailwindRule,
|
|
243
|
+
tailwindSupport,
|
|
244
|
+
buildEntry,
|
|
245
|
+
BASE_CSS,
|
|
246
|
+
CACHE_DIR,
|
|
247
|
+
ENTRY_FILE,
|
|
248
|
+
CANDIDATES_FILE,
|
|
249
|
+
PUBLIC_SPECIFIER,
|
|
250
|
+
};
|