|
| 1 | +--- |
| 2 | +title: Lume 3 |
| 3 | +author: Óscar Otero |
| 4 | +draft: true |
| 5 | +tags: |
| 6 | + - Releases |
| 7 | +comments: {} |
| 8 | +--- |
| 9 | + |
| 10 | +## About loaded and copied files |
| 11 | + |
| 12 | +When Lume builds a site, some files are **loaded** and others **copied**. |
| 13 | + |
| 14 | +Files with extensions `.vto`, `.md`, or `.page.ts` are loaded because Lume needs |
| 15 | +to read the content to generate HTML pages. They are known as _Page files_. |
| 16 | + |
| 17 | +Files added using the function `site.copy()` are just copied from the `src` |
| 18 | +folder to `dest` without reading the content, which is faster and consumes less |
| 19 | +memory. These files are known as _Static files_. |
| 20 | + |
| 21 | +However, there are some files that must be loaded or copied, depending on the |
| 22 | +configuration. For example, CSS files are loaded if you use a plugin like |
| 23 | +Postcss, because the content needs to be processed. In this case, the plugin |
| 24 | +internally runs `site.loadAssets([".css"])`, to instruct Lume that the files |
| 25 | +with extension `.css` need to be loaded but not to generate HTML pages but |
| 26 | +assets. |
| 27 | + |
| 28 | +One of the most recurrent issues is when a file is configured to be copied and |
| 29 | +loaded. For example: |
| 30 | + |
| 31 | +```js |
| 32 | +site.copy([".css"]); |
| 33 | +site.use(postcss()); |
| 34 | +``` |
| 35 | + |
| 36 | +In this configuration, the build copies all CSS files and you may expect these |
| 37 | +files to be transformed with the postcss plugin. However due to the `.css` files |
| 38 | +being copied, not loaded, **the plugin doesn't process them**. |
| 39 | + |
| 40 | +Another example, a bit less obvious is when you have a folder with different |
| 41 | +files, some of them need to be processed and others don't: |
| 42 | + |
| 43 | +```js |
| 44 | +site.copy("/assets"); |
| 45 | +site.use(postcss()); |
| 46 | +``` |
| 47 | + |
| 48 | +In this case, we are copying all files in the `/assets` folder. This folder can |
| 49 | +contain CSS files that, as you may guess, won't be processed by Postcss. |
| 50 | + |
| 51 | +This behavior is confusing, but fixing it is a breaking change, so this is the |
| 52 | +main reason version 3 was released. Lume should be clever enough to don't |
| 53 | +delegate the decision of whether a file must be loaded or copied to you. |
| 54 | + |
| 55 | +## Introducing `add()` |
| 56 | + |
| 57 | +In Lume 3 all this logic was refactored and the functions `site.loadAssets()` |
| 58 | +and `site.copy()` were replaced with a single function: `site.add()`. |
| 59 | + |
| 60 | +The `add()` function simply instructs Lume that you want to include this file in |
| 61 | +your site, but without specifying how this file must be treated. Lume will load |
| 62 | +the file if it needs to (for example, if it needs to be processed), or will copy |
| 63 | +it if no transformation needs to be made. |
| 64 | + |
| 65 | +This simplifies a lot the configuration, especially in those cases where copied |
| 66 | +and loaded files are mixed in the same folder. |
| 67 | + |
| 68 | +`site.add()` has the same syntax as the old `site.copy()`, so you can add all |
| 69 | +CSS files that are loaded and processed by Postcss: |
| 70 | + |
| 71 | +```js |
| 72 | +site.add([".css"]); |
| 73 | +site.use(postcss()); |
| 74 | +``` |
| 75 | + |
| 76 | +You can add also specific files and folders, and even change the destination |
| 77 | +folder: |
| 78 | + |
| 79 | +```js |
| 80 | +// Add all files in /assets to the root of dest folder. |
| 81 | +site.add("/assets", "."); |
| 82 | + |
| 83 | +// Run Postcss. Any CSS file in /assets will be processed! |
| 84 | +site.use(postcss()); |
| 85 | +``` |
| 86 | + |
| 87 | +### Replacing `copyRemainingFiles()` |
| 88 | + |
| 89 | +In Lume 2 there was the `site.copyRemainingFiles()` function as a way to manage |
| 90 | +complex situations like this. For example, let's say you have the following |
| 91 | +structure: |
| 92 | + |
| 93 | +```txt |
| 94 | +|_ articles/ |
| 95 | + |_ article-1/ |
| 96 | + | |_ index.md |
| 97 | + | |_ picture.jpg |
| 98 | + | |_ document.pdf |
| 99 | + | |_ foo32.gif |
| 100 | + |_ article-2/ |
| 101 | + |_ index.md |
| 102 | + |_ journey.mp4 |
| 103 | + |_ download.zip |
| 104 | +``` |
| 105 | + |
| 106 | +In this structure we want to render all markdown files and copy the remaining |
| 107 | +files. In Lume 2 we cannot do `site.copy("articles")`, because the `index.md` |
| 108 | +files inside these folders wouldn't be processed (they would be treated as |
| 109 | +static files). So we had to use the `copyRemainingFiles` function. |
| 110 | + |
| 111 | +```js |
| 112 | +// Lume 2 |
| 113 | +site.copyRemainingFiles( |
| 114 | + (path: string) => path.startsWith("/articles/"), |
| 115 | +); |
| 116 | +``` |
| 117 | + |
| 118 | +With this configuration, any file not loaded that is inside the `/articles` |
| 119 | +folder will be copied. |
| 120 | + |
| 121 | +In Lume 3, we can simply do: |
| 122 | + |
| 123 | +```js |
| 124 | +// Lume 3 |
| 125 | +site.add("articles"); |
| 126 | +``` |
| 127 | + |
| 128 | +**And that's all!** Any file that must be loaded (like `.md` files) will be |
| 129 | +loaded. And the others simply will be copied. |
| 130 | + |
| 131 | +### Copy remote files |
| 132 | + |
| 133 | +`site.add()` not only can add files from the `src` folder but also remote files. |
| 134 | +In Lume 2 this was possible using `remoteFile` function: |
| 135 | + |
| 136 | +```js |
| 137 | +// Lume 2 |
| 138 | +site.remoteFile("styles.css", "https://example.com/theme/styles.css"); |
| 139 | +site.copy("styles.css"); |
| 140 | +``` |
| 141 | + |
| 142 | +Lume 3 makes this use case more simple: |
| 143 | + |
| 144 | +```js |
| 145 | +// Lume 3 |
| 146 | +site.add("https://example.com/theme/styles.css", "styles.css"); |
| 147 | +``` |
| 148 | + |
| 149 | +The `site.add()` function accepts also `npm` specifiers: |
| 150 | + |
| 151 | +```js |
| 152 | +site.add("npm:normalize.css", "/styles/normalize.css"); |
| 153 | +``` |
| 154 | + |
| 155 | +Internally, uses jsDelivr to download the file. In this example, |
| 156 | +`npm:normalize.css` is transformed to |
| 157 | +`https://cdn.jsdelivr.net/npm/normalize.css`. |
| 158 | + |
| 159 | +> [!note] |
| 160 | +> |
| 161 | +> `site.remoteFile` is still useful in Lume 3, especially for other file types |
| 162 | +> like `_data` files, templates, components, etc. |
| 163 | +
|
| 164 | +## Add files explicitly |
| 165 | + |
| 166 | +In Lume 2, some plugins configure Lume to load files with a certain extension |
| 167 | +automatically. For example, Postcss configures Lume to load all CSS files: |
| 168 | + |
| 169 | +```js |
| 170 | +// Lume 2 |
| 171 | + |
| 172 | +// All .css files are loaded and processed |
| 173 | +site.use(postcss()); |
| 174 | +``` |
| 175 | + |
| 176 | +In most cases, this is what you want. But if you don't want to output all CSS |
| 177 | +files, you have to use the `ignore()` function. |
| 178 | + |
| 179 | +For example, let's say we have the following structure and only want to process |
| 180 | +`homepage.css` and `about.css` but not the files inside `utils` (because they |
| 181 | +are already imported): |
| 182 | + |
| 183 | +```txt |
| 184 | +|_ styles/ |
| 185 | + |_ homepage.css |
| 186 | + |_ about.css |
| 187 | + |_ utils/ |
| 188 | + |_ typography.css |
| 189 | + |_ color.css |
| 190 | +``` |
| 191 | + |
| 192 | +In Lume 2 we have to ignore this folder. |
| 193 | + |
| 194 | +```js |
| 195 | +// Lume 2 |
| 196 | +site.use(postcss()); |
| 197 | +site.ignore("styles/utils"); |
| 198 | +``` |
| 199 | + |
| 200 | +The problem with this behavior is that Lume by default adds everything and then |
| 201 | +you have to ignore what you don't want. Sometimes this approach causes more harm |
| 202 | +than benefit. |
| 203 | + |
| 204 | +In Lume 3, thanks to the `site.add()` function, it's very easy to add new files |
| 205 | +(and only the files that you want), so plugins **no longer load files by |
| 206 | +default**. You have to explicitly add them, which is more clear and intuitive: |
| 207 | + |
| 208 | +```js |
| 209 | +// Lume 3 |
| 210 | + |
| 211 | +site.use(postcss()); |
| 212 | +site.add("styles/homepage.css"); |
| 213 | +site.add("styles/about.css"); |
| 214 | +``` |
| 215 | + |
| 216 | +Or if you want to load all css files: |
| 217 | + |
| 218 | +```js |
| 219 | +// Lume 3 |
| 220 | + |
| 221 | +site.use(postcss()); |
| 222 | +site.add(".css"); |
| 223 | +``` |
| 224 | + |
| 225 | +Another benefit is you have better control of all entry points of your assets. |
| 226 | +For example, for esbuild: |
| 227 | + |
| 228 | +```js |
| 229 | +// Lume 3 |
| 230 | + |
| 231 | +site.use(esbuild()); |
| 232 | +site.add("main.ts"); // Only this file will be bundled |
| 233 | +``` |
| 234 | + |
| 235 | +This change affects to the following plugins: `svgo`, `transform_images`, |
| 236 | +`picture`, `postcss`, `sass`, `tailwindcss`, `unocss`, `esbuild` and `terser`. |
| 237 | + |
| 238 | +## One JSX |
| 239 | + |
| 240 | +Lume started supporting `JSX` as a template engine thanks to the `jsx` plugin |
| 241 | +that uses React under the hood. The reason for choosing React is because it's |
| 242 | +the default library, so it worked without any configuration. |
| 243 | + |
| 244 | +Some versions later, Lume added support for Preact, a smaller and more |
| 245 | +performant React alternative, with the plugin `jsx_preact`. Having two JSX |
| 246 | +plugins for the same purpose is not good, and adds unnecessary complexity (for |
| 247 | +example to use MDX plugin). |
| 248 | + |
| 249 | +In addition to that, both libraries are frontend-first libraries, with features |
| 250 | +like hooks, events callbacks, hydration, etc, that are not supported on the |
| 251 | +server side, and some people were confused about what they are able to do with |
| 252 | +them in Lume. |
| 253 | + |
| 254 | +Lume 3 has only one JSX library, and it's not React or Preact. It's |
| 255 | +[SSX](https://github.com/oscarotero/ssx/) a library created specifically for |
| 256 | +static sites, created in TypeScript, faster than React and Preact |
| 257 | +[See Benchmarks](https://github.com/oscarotero/ssx/actions/runs/13022300332/job/36325328553#step:7:22) |
| 258 | +and more ergonomic. It allows to create asynchronous components, insert raw code |
| 259 | +like `<!doctype html>`, and comes with great documentation including all HTML |
| 260 | +elements and attributes, with links to MDN. |
| 261 | + |
| 262 | +And because Lume has only a JSX library, the MDX plugin works automatically |
| 263 | +without needing to use a JSX plugin before. |
| 264 | + |
| 265 | +## Improved Lume components |
| 266 | + |
| 267 | +### Async components |
| 268 | + |
| 269 | +One of the main limitations of Lume components was the synchronous nature. The |
| 270 | +reason is to support JSX components that were synchronous in React and Preact. |
| 271 | +With SSX, we don't have this limitation any more and all components are async. |
| 272 | + |
| 273 | +For example, you can create a component in JSX that returns a promise: |
| 274 | + |
| 275 | +```jsx |
| 276 | +// _components/salute.jsx |
| 277 | + |
| 278 | +export default async function ({ id }) { |
| 279 | + const response = await fetch(`https://example.com/api?id=${id}`); |
| 280 | + const data = await response.json(); |
| 281 | + return <strong>Hello {data.name}</strong>; |
| 282 | +} |
| 283 | +``` |
| 284 | + |
| 285 | +And this component can be used in any other template engine, like JSX: |
| 286 | + |
| 287 | +```jsx |
| 288 | +export default async function ({ comp }) { |
| 289 | + return ( |
| 290 | + <p> |
| 291 | + <comp.Salute id="23" /> |
| 292 | + </p> |
| 293 | + ); |
| 294 | +} |
| 295 | +``` |
| 296 | + |
| 297 | +Or Vento: |
| 298 | + |
| 299 | +```html |
| 300 | +<p>{{ await comp.Salute({ id: 23}) }}</p> |
| 301 | +``` |
| 302 | + |
| 303 | +### Folder component |
| 304 | + |
| 305 | +Lume components not only generate HTML code to reuse but can also export the CSS |
| 306 | +and JS code needed to run this code on the browser. The code must be exported in |
| 307 | +the variables `css` and `js`. For example: |
| 308 | + |
| 309 | +```md |
| 310 | +--- |
| 311 | +css: | |
| 312 | + .mainTitle { |
| 313 | + color: red; |
| 314 | + } |
| 315 | +--- |
| 316 | + |
| 317 | +<h1 class="mainTitle">{{ name }}</h1> |
| 318 | +``` |
| 319 | + |
| 320 | +The problem of this approach is the CSS and JS code is not treated as CSS and JS |
| 321 | +code by the code editor, so there's no syntax highlight. |
| 322 | + |
| 323 | +In Lume 3 it's possible to generate a component in a folder, with the CSS and JS |
| 324 | +code in different files. To do that, you have to keep the following structure: |
| 325 | + |
| 326 | +```txt |
| 327 | +|_ _components/ |
| 328 | + |_ button/ |
| 329 | + |_ comp.vto |
| 330 | + |_ style.css |
| 331 | + |_ script.js |
| 332 | +``` |
| 333 | + |
| 334 | +Any folder containing a `comp.*` file will be loaded as a component using the |
| 335 | +folder name as the component name. And the files `style.css` and `script.js` |
| 336 | +will be loaded as the CSS and JS code for the component. This makes the creation |
| 337 | +of components more ergonomic, specially for cases with a lot of CSS and JS code. |
| 338 | + |
| 339 | +Now you can use the component anywhere: |
| 340 | + |
| 341 | +```js |
| 342 | +{ |
| 343 | + { |
| 344 | + await comp.Button({ content: "click here" }); |
| 345 | + } |
| 346 | +} |
| 347 | +``` |
| 348 | + |
| 349 | +## Tailwind 4 |
| 350 | + |
| 351 | +## Processors improvements |
0 commit comments