From 8636d6112b7f2498ee85504a8eaca702e4010d02 Mon Sep 17 00:00:00 2001 From: Anton Evzhakov Date: Tue, 5 Dec 2023 15:43:12 +0200 Subject: [PATCH] doc: bundlers and configuration --- apps/website/pages/bundlers/esbuild.mdx | 30 +++ apps/website/pages/bundlers/rollup.mdx | 48 ++++ apps/website/pages/bundlers/svelte.mdx | 84 +++++++ apps/website/pages/bundlers/vite.mdx | 41 ++++ apps/website/pages/configuration.mdx | 309 ++++++++++++++++++++++++ apps/website/pages/feature-flags.mdx | 70 ++++++ 6 files changed, 582 insertions(+) create mode 100644 apps/website/pages/bundlers/esbuild.mdx create mode 100644 apps/website/pages/bundlers/rollup.mdx create mode 100644 apps/website/pages/bundlers/svelte.mdx create mode 100644 apps/website/pages/bundlers/vite.mdx create mode 100644 apps/website/pages/configuration.mdx create mode 100644 apps/website/pages/feature-flags.mdx diff --git a/apps/website/pages/bundlers/esbuild.mdx b/apps/website/pages/bundlers/esbuild.mdx new file mode 100644 index 00000000..85dbcb58 --- /dev/null +++ b/apps/website/pages/bundlers/esbuild.mdx @@ -0,0 +1,30 @@ +# Usage with esbuild + +### Installation + +```sh npm2yarn +npm i -D @wyw-in-js/esbuild +``` + +### Configuration + +```js +import wyw from '@wyw-in-js/esbuild'; +import esbuild from 'esbuild'; + +const prod = process.env.NODE_ENV === 'production'; + +esbuild + .build({ + entryPoints: ['src/index.ts'], + outdir: 'dist', + bundle: true, + minify: prod, + plugins: [ + wyw({ + sourceMap: prod, + }), + ], + }) + .catch(() => process.exit(1)); +``` diff --git a/apps/website/pages/bundlers/rollup.mdx b/apps/website/pages/bundlers/rollup.mdx new file mode 100644 index 00000000..110bfcfe --- /dev/null +++ b/apps/website/pages/bundlers/rollup.mdx @@ -0,0 +1,48 @@ +# Usage with Rollup + +### Installation + +To use WyW-in-JS with Rollup, you need to use it together with a plugin which handles CSS files, such as `rollup-plugin-css-only`: + +```sh npm2yarn +npm i -D rollup-plugin-css-only @wyw-in-js/rollup +``` + +### Configuration + +```js +import wyw from '@wyw-in-js/rollup'; +import css from 'rollup-plugin-css-only'; + +export default { + plugins: [ + wyw({ + sourceMap: process.env.NODE_ENV !== 'production', + }), + css({ + output: 'styles.css', + }), + ], +}; +``` + +If you are using [@rollup/plugin-babel](https://github.com/rollup/plugins/tree/master/packages/babel) as well, ensure the wyw plugin is declared earlier in the `plugins` array than your babel plugin. + +```js +import wyw from '@wyw-in-js/rollup'; +import css from 'rollup-plugin-css-only'; +import babel from '@rollup/plugin-babel'; + +export default { + plugins: [ + wyw({ + sourceMap: process.env.NODE_ENV !== 'production', + }), + css({ + output: 'styles.css', + }), + babel({}), + /* rest of your plugins */ + ], +}; +``` diff --git a/apps/website/pages/bundlers/svelte.mdx b/apps/website/pages/bundlers/svelte.mdx new file mode 100644 index 00000000..2a5847fd --- /dev/null +++ b/apps/website/pages/bundlers/svelte.mdx @@ -0,0 +1,84 @@ +# Usage with Svelte + +#### Contents + +- [Svelte with Rollup](#Rollup-1) +- [Svelte with Webpack](#Webpack-1) + +#### Rollup + +Take a look: [d964432](https://github.com/madhavarshney/svelte-linaria-sample/commit/d96443218694c0c8d80edf7c40a8fbf7c1f6997f) + +Install `rollup-plugin-css-only` and update `rollup.config.js` + +```js +import svelte from 'rollup-plugin-svelte'; +import css from 'rollup-plugin-css-only'; // for CSS bundling +import wyw from '@wyw-in-js/rollup'; + +const dev = process.env.NODE_ENV !== 'production'; + +export default { + ... + plugins: [ + svelte({ + dev, + // allow `plugin-css-only` to bundle with CSS generated by wyw + emitCss: true, + }), + wyw({ + sourceMap: dev, + }), + css({ + output: '/bundle.css', + }), + ], +}; +``` + +**IMPORTANT**: `rollup-plugin-css-only` generates incorrect sourcemaps (see [thgh/rollup-plugin-css-only#10](https://github.com/thgh/rollup-plugin-css-only/issues/10)). Use an alternative CSS plugin such as [`rollup-plugin-postcss`](https://github.com/egoist/rollup-plugin-postcss) instead in the same way as above. + +#### Webpack + +Take a look: [5ffd69d](https://github.com/madhavarshney/svelte-linaria-sample/commit/5ffd69dc9f9584e3eec4127e798d7a4c1552ec19) + +Update `webpack.config.js` with the following: + +```js +const prod = process.env.NODE_ENV === 'production'; + +const wywLoader = { + loader: '@wyw-in-js/webpack-loader', + options: { + sourceMap: !prod, + }, +}; + +module.exports = { + ... + module: { + rules: [ + { + test: /\.m?js$/, + exclude: /node_modules/, + use: [wywLoader], + }, + { + test: /\.svelte$/, + use: [ + wywLoader, + { + loader: 'svelte-loader', + options: { + dev: !prod, + emitCss: true, + hotReload: true, + }, + }, + ], + }, + ...(CSS rules) + ], + }, +}; +``` diff --git a/apps/website/pages/bundlers/vite.mdx b/apps/website/pages/bundlers/vite.mdx new file mode 100644 index 00000000..a10d3498 --- /dev/null +++ b/apps/website/pages/bundlers/vite.mdx @@ -0,0 +1,41 @@ +# Usage with Vite + +### Installation + +~~Since Vite supports Rollup plugin~~ Since Vite provides more features and flexibility, WyW-in-JS has a separate plugin for it `@wyw-in-js/vite`. Vite handles CSS by itself, you don't need a css plugin. + +```sh npm2yarn +npm i -D @wyw-in-js/vite +``` + +### Configuration + +```js +import { defineConfig } from 'vite'; +import wyw from '@wyw-in-js/vite'; + +export default defineConfig(() => ({ + // ... + plugins: [wyw()], +})); +``` + +If you are using language features that requires a babel transform (such as typescript), ensure the proper babel presets or plugins are passed to wyw. + +```js +import { defineConfig } from 'vite'; +import wyw from '@wyw-in-js/vite'; + +// example to support typescript syntax: +export default defineConfig(() => ({ + // ... + plugins: [ + wyw({ + include: ['**/*.{ts,tsx}'], + babelOptions: { + presets: ['@babel/preset-typescript', '@babel/preset-react'], + }, + }), + ], +})); +``` diff --git a/apps/website/pages/configuration.mdx b/apps/website/pages/configuration.mdx new file mode 100644 index 00000000..71f55fa7 --- /dev/null +++ b/apps/website/pages/configuration.mdx @@ -0,0 +1,309 @@ +# Configuration + +WyW-in-JS can be customized using a JavaScript, JSON or YAML file. This can be in form of: + +- `wyw-in-js.config.js` JS file exporting the object (recommended). +- `wyw-in-js` property in a `package.json` file. +- `.wyw-in-jsrc` file with JSON or YAML syntax. +- `.wyw-in-jsrc.json`, `.wyw-in-jsrc.yaml`, `.wyw-in-jsrc.yml`, or `.wyw-in-jsrc.js` file. + +Example `wyw-in-js.config.js`: + +```js +module.exports = { + evaluate: true, + displayName: false, +}; +``` + +## Options + +- `evaluate: boolean` (default: `true`): + +Enabling this will evaluate dynamic expressions in the CSS. You need to enable this if you want to use imported variables in the CSS or interpolate other components. Enabling this also ensures that your styled components wrapping other styled components will have the correct specificity and override styles properly. + +- `displayName: boolean` (default: `false`): + +Enabling this will add a display name to generated class names, e.g. `.Title_abcdef` instead of `.abcdef'. It is disabled by default to generate smaller CSS files. + +- `variableNameConfig: "var" | "dashes" | "raw"` (default: `var`): + +Configures how the variable will be formatted in final CSS. + +### Possible values + +#### `var` +Use full css variable structure. This is default behavior. + +```js +import { styled } from "@linaria/react"; + +export const MyComponent = styled.div` + color: ${(props) => props.color}; +`; + ``` + +In CSS you will see full variable declaration + +```css +.MyComponent_m1cus5as { + color: var(--m1cus5as-0); +} + ``` + +#### `dashes` +Removes `var()` around the variable name. It is useful when you want to control the variable on your own. For example you can set default value for CSS variable. + +```js +import { styled } from "@linaria/react"; + +export const Theme = styled.div` + --font-color: red; +`; + +export const MyComponent = styled.div` + // Returning empty string is mandatory + // Otherwise you will have "undefined" in css variable value + color: var(${(props) => props.color ?? ""}, var(--font-color)); +`; + +function App() { + return ( + + Text with red color + Text with blue color + + ); +} + ``` + +In CSS you will see generated variable name and your default value. + +```css +.Theme_t1cus5as { + --font-color: red; +} + +.MyComponent_mc195ga { + color: var(--mc195ga-0, var(--font-color)); +} + ``` + +#### `raw` +Use only variable name without dashes and `var()` wrapper. + +```js +import { styled } from "@linaria/react"; + +export const MyComponent = styled.div` + color: ${(props) => props.color}; +`; + ``` + +In CSS you will see just the variable name. This is not valid value for css property. + +```css +.MyComponent_mc195ga { + color: mc195ga-0; +} + ``` + +You will have to make it valid: + +```js +export const MyComponent = styled.div` + color: var(--${(props) => props.color}); +`; + ``` + +- `classNameSlug: string | ((hash: string, title: string, args: ClassNameSlugVars) => string)` (default: `default`): + +Using this will provide an interface to customize the output of the CSS class name. Example: + + classNameSlug: '[title]', + +Would generate a class name such as `.header` instead of the default `.header_absdjfsdf` which includes a hash. + +You may also use a function to define the slug. The function will be evaluated at build time and must return a string: + + classNameSlug: (hash, title) => `${hash}__${7 * 6}__${title}`, + +Would generate the class name `.absdjfsdf__42__header`. + +Last argument `args` is an object that contains following properties: `title`, `hash`, `dir`, `ext`, `file`, `name`. These properties +are useful when you want to generate your own hash: + +```js +const sha1 = require("sha1"); + +module.exports = { + classNameSlug: (hash, title, args) => sha1(`${args.name}-${title}`) +}; + ``` + +**note** invalid characters will be replaced with an underscore (`_`). + +### Variables + +- `hash`: The hash of the content. +- `title`: The name of the class. + +- `variableNameSlug: string | ((context: IVariableContext) => string)` (default: `default`): + +Using this will provide an interface to customize the output of the CSS variable name. Example: + + variableNameSlug: '[componentName]-[valueSlug]-[index]', + +Would generate a variable name such as `--Title-absdjfsdf-0` instead of the `@react/styled`'s default `--absdjfsdf-0`. + +You may also use a function to define the slug. The function will be evaluated at build time and must return a string: + + variableNameSlug: (context) => `${context.valueSlug}__${context.componentName}__${context.precedingCss.match(/([\w-]+)\s*:\s*$/)[1]}`, + +Would generate the variable name `--absdjfsdf__Title__flex-direction`. + +**note** invalid characters will be replaced with an underscore (`_`). + +### Variables + +- `componentName` - the displayName of the component. +- `componentSlug` - the component slug. +- `index` - the index of the css variable in the current component. +- `precedingCss` - the preceding part of the css for the variable, i.e. `flex: 1; flex-direction: `. +- `preprocessor` - the preprocessor used to process the tag (e.g. 'StyledProcessor' or 'AtomicStyledProcessor'). +- `source` - the string source of the css property value. +- `unit` - the unit. +- `valueSlug` - the value slug. + + +- `overrideContext: (context: Partial, filename: string) => Partial` + +A custom function to override the context used to evaluate modules. This can be used to add custom globals or override the default ones. + +```js +module.exports = { + overrideContext: (context, filename) => ({ + ...context, + HighLevelAPI: () => "I'm a high level API", + }), +}; + ``` + +- `rules: EvalRule[]` + +The set of rules that defines how the matched files will be transformed during the evaluation. +`EvalRule` is an object with two fields: + +- `test` is a regular expression or a function `(path: string) => boolean`; +- `action` is an `Evaluator` function, `"ignore"` or a name of the module that exports `Evaluator` function as a default export. + +If `test` is omitted, the rule is applicable for all the files. + +The last matched rule is used for transformation. If the last matched action for a file is `"ignore"` the file will be evaluated as is, so that file must not contain any js code that cannot be executed in nodejs environment (it's usually true for any lib in `node_modules`). + +If you need to compile certain modules under `/node_modules/` (which can be the case in monorepo projects), it's recommended to do it on a module by module basis for faster transforms, e.g. `ignore: /node_modules[\/\\](?!some-module|other-module)/`. Example is using Regular Expressions negative lookahead. + +The Information about `Evaluator`, its default setting and custom implementations can be founded it [evaluators section of How it works docs](/how-it-works#evaluators) + +The default setup is: + +```js +import { shaker } from '@wyw-in-js/transform'; + +[ + { + action: shaker, + }, + { + test: /[\\/]node_modules[\\/]/, + action: 'ignore', + }, + { + test: (filename, code) => { + if (!/[\\/]node_modules[\\/]/.test(filename)) { + return false; + } + + return /(?:^|\*\/|;|})\s*(?:export|import)[\s{]/m.test(code); + }, + action: shaker, + } +]; + ``` + +- `tagResolver: (source, tag) => string` + +A custom function to use when resolving template tags. + +By default, linaria APIs like `css` and `styled` **must** be imported directly from the package – this is because babel needs to be able to recognize the API's to do static style extraction. `tagResolver` allows `css` and `styled` APIs to be imported from other files too. + +`tagResolver` takes the path for the source module (eg. `@linaria/core`) and the name of imported tag (eg. `css`), and returns the full path to the related processor. If `tagResolver` returns `null`, the default tag processor will be used. + +For example, we can use this to map `@linaria/core` , `@linaria/react` or `@linaria/atomic` where we re-export the module. + + ```js + { + tagResolver: (source, tag) => { + const pathToLocalFile = join(__dirname, './my-local-folder/linaria.js'); + if (source === pathToLocalFile) { + if (tag === 'css') { + return require.resolve('@linaria/core/processors/css'); + } + + if (tag === 'styled') { + return require.resolve('@linaria/react/processors/styled'); + } + } + + return null; + }; + } + ``` + +We can then re-export and use linaria API's from `./my-local-folder`: + + ```js + // my-file.js + import { css, styled } from './my-local-folder/linaria'; + + export const MyComponent = styled.div` + color: red; + `; + + export default css` + border: 1px solid black; + `; + ``` + + ```js + // ./my-local-folder/core.js + export * from '@linaria/core'; + ``` + +- `babelOptions: Object` + +If you need to specify custom babel configuration, you can pass them here. These babel options will be used by Linaria when parsing and evaluating modules. + +- `features: Record` + +A map of feature flags to enable/disable. See [Feature Flags](/feature-flags) for more information. + +## `@wyw-in-js/babel-preset` + +The preset pre-processes and evaluates the CSS. The bundler plugins use this preset under the hood. You also might want to use this preset if you import the components outside of the files handled by your bundler, such as on your server or in unit tests. + +To use this preset, add `@wyw-in-js/babel-preset` to your Babel configuration at the end of the presets list: + +`.babelrc`: + +```diff +{ + "presets": [ + "@babel/preset-env", + "@babel/preset-react", ++ "@wyw-in-js" + ] +} +``` + +The babel preset can accept the same options supported by the configuration file, however it's recommended to use the configuration file directly. diff --git a/apps/website/pages/feature-flags.mdx b/apps/website/pages/feature-flags.mdx new file mode 100644 index 00000000..8715ed27 --- /dev/null +++ b/apps/website/pages/feature-flags.mdx @@ -0,0 +1,70 @@ +# Feature Flags + +Feature flags are used to enable or disable specific features provided. The `features` option in the configuration allows you to control the availability of these features. + +## Syntax for Specifying Flags + +- `true`: Enables the feature for all files. +- `false`: Disables the feature for all files. +- `"glob"`: Enables the feature only for files that match the specified glob pattern. +- `["glob1", "glob2"]`: Enables the feature for files matching any of the specified glob patterns. +- `["glob1", "!glob2"]`: Enables the feature for files matching `glob1` but excludes files that match `glob2`. + + +# `dangerousCodeRemover` Feature + +The `dangerousCodeRemover` is a flag that is enabled by default. It is designed to enhance the static evaluation of values that are interpolated in styles and to optimize the processing of styled-wrapped components during the build stage. This optimization is crucial for maintaining a stable and fast build process. It is important to note that the `dangerousCodeRemover` does not impact the runtime code; it solely focuses on the code used during the build. + +## How It Works + +During the build process, WyW-in-JS statically analyzes the CSS-in-JS codebase and evaluates the styles and values that are being interpolated. The `dangerousCodeRemover` steps in at this stage to remove potentially unsafe code, which includes code that might interact with browser-specific APIs, make HTTP requests, or perform other runtime-specific operations. By removing such code, the evaluation becomes more reliable, predictable, and efficient. + +## Benefits + +Enabling the `dangerousCodeRemover` feature provides several benefits: + +1. **Stability**: The removal of potentially unsafe code ensures that the build process remains stable. It minimizes the chances of encountering build-time errors caused by unsupported browser APIs or non-static operations. + +2. **Performance**: Removing unnecessary code results in faster build times. The build tool can efficiently process and evaluate the styles and components without unnecessary overhead, leading to quicker development cycles. + +## Fine-Tuning the Removal + +While the `dangerousCodeRemover` is highly effective at optimizing the build process, there may be cases where it becomes overly aggressive and removes code that is actually required for your specific use case. In such situations, you have the flexibility to fine-tune the behavior of the remover. + +By leveraging the `features` option in the configuration, you can selectively disable the `dangerousCodeRemover` for specific files. This allows you to preserve valuable code that may not be safely evaluated during the build process. + +### Example + +Suppose you have a file named `specialComponent.js` that contains code that should not be deleted. By adding the following entry to your `features` configuration: + +```js +{ + features: { + dangerousCodeRemover: ["**/*", "!**/specialComponent.js"], + }, +} +``` + +You are instructing WyW-in-JS to exclude the `specialComponent.js` file from the removal process. As a result, any code within this file that would have been removed by the `dangerousCodeRemover` will be retained in the build output. + + +# `globalCache` Feature + +The `globalCache` is enabled by default. WyW-in-JS uses two levels of caching to improve the performance of the build process. The first level is used to cache transformation and evaluation results for each `transform` call, usually a single call of Webpack's loader or Vite's transform hook. The second level is used to cache the results of the entire build process. The `globalCache` feature controls the second level of caching. Turning off this feature will result in a slower build process but decreased memory usage. + + +# `happyDOM` Feature + +The `happyDOM` is enabled by default. This feature enables usage of https://github.com/capricorn86/happy-dom to emulate a browser environment during the build process. Typically, the `dangerousCodeRemover` feature should remove all browser-related code. However, some libraries may still contain browser-specific code that cannot be statically evaluated. In such cases, the `happyDOM` feature can be used to emulate a browser environment during the build process. This allows WyW-in-JS to evaluate the code without encountering errors caused by missing browser APIs. + + +# `softErrors` Feature + +The `softErrors` is disabled by default. It is designed to provide a more lenient evaluation of styles and values that are interpolated in styles. This flag is useful for debugging and prevents the build from failing even if some files cannot be processed with WyW-in-JS. + + +# 'useBabelConfigs' Feature + +The `useBabelConfigs` feature is enabled by default. If it is enabled, WyW-in-JS will try to resolve the `.babelrc` file for each processed file. Otherwise, it will use the default Babel configuration from `babelOptions` in the configuration. + +Please note that the default value of `useBabelConfigs` will be changed to `false` in the next major release.