</svg> <p class="callout__text"> Without all the feature of SASS being used, you can still use PostCSS. Using postcss-sass it is able to take .scss file into .css
With the project layout as below, the first step is using transpile the SASS file into CSS.
. ├── package.json ├── src │ ├── _data │ │ ├── site.json │ │ └── ... │ ├──- _includes │ │ ├── layouts │ │ │ ├── default.njk │ │ | └── ... │ │ └── partials │ │ ├── archives.njk │ │ └── ... │ ├── images │ │ ├── favicon.ico │ │ └── ... │ ├── index.njk │ ├── posts │ ├── scripts │ │ ├── main.js │ │ ├── modules │ │ │ └── lazyload │ │ │ └── index.js │ │ └── utilities │ │ ├── filters.js │ │ └── utils.js │ ├── styles │ ├── base │ │ ├── _all.scss │ │ ├── ... │ │ └── _utilities.scss │ ├── components │ │ ├── _all.scss │ │ ├── ... │ │ └── _webmention.scss │ └── main.scss └── tailwind.config.js $ sass src/styles/main.scss tmp/main.css Since I am using TailwindCSS. I need to transform its import such as @tailwind base into TailwindCSS classes using PostCSS.
postcss --config postcss.config.js tmp/main.css --output dist/styles/main.css The next step is to concatenate and uglify the Javascript. Indeed I can not do it by hand. I use esbuild for this.
./build.js I use aliases in Javascript code. So I need to tell the absolute path to esbuild.
let onResolvePlugin = { name : "resolvePath" , setup ( build ) { let path = require ( "path" ) // Redirect all paths starting with "@modules/" to "src/scripts/modules" build . onResolve ( { filter : / ^ @modules \// }, ( args ) => { // get such `@modules/mobile-nav` without the `@module` part let moduleName = args . path . split ( "/" )[ 1 ] return { path : path . resolve ( "src/scripts/modules" , moduleName , "index.js" ) } } ) build . onResolve ( { filter : / ^ @utilities \// }, ( args ) => { let moduleName = args . path . split ( "/" )[ 1 ] return { path : path . resolve ( "src/scripts/utilities" , moduleName , "index.js" ) } } ) }, } Unfortunately, using --watch in dart-sass make it block other processes. Even if I run those commands asynchronously in bash.
# build.sh # dart-sass block other command from running even if I am using `&` npx sass src/styles/main.scss tmp/main.css --watch & npx postcss --config postcss.config.js tmp/main.css --output dist/styles/main.css --watch --verbose & ./build.js & npx eleventy --serve My workaround is using nodemon: npx nodemon --watch src -x 'npm run dev'. The final package.json looks like this:
// package.json { " scripts ": { " dev:sass ": "sass src/styles/main.scss tmp/main.css" , " dev:styles ": "postcss --config postcss.config.js tmp/main.css --output dist/styles/main.css" , " dev:scripts ": "./build.js" , " dev ": "npm run dev:sass && npm run dev:styles && npm run dev:scripts && eleventy --serve" , " dev-watch ": "npx nodemon --watch src -x 'npm run dev'" } } The downside of using nodemon is that it does not know the context. It just reloads if anything changes. So it does not know which part is changed, and which part needs to be rebuild. It just rebuilds everything . Which is very slow.
I don't want to spend my time waiting for my site to build. I need a better solution.
Parcel I use Parcel 2 beta 3 which is 10x faster, thanks to the previous compiler being replaced with Rust SWC.
I start by transpiling the style.
$ parcel watch 'src/styles/main.scss' --dist-dir 'dist/styles' It has some issue with Cannot load file. All the current filename in the codebase doesn't point to the concrete file. Because other bundler doesn't care about it.
# Parcel error with awesome suggestion @parcel/resolver-default: Cannot load file '../../images/icons/warning.svg' in './src/styles'. Did you mean ../images/icons/warning.svg? . twa-warning { background-image : url ( "/images/icons/warning.svg" ); } To solve the issue, I need to point them to the concrete file and pass --public-path in the command.
. twa-warning { background-image : url ( "../images/icons/warning.svg" ); } parcel watch 'src/styles/main.scss' --dist-dir 'dist/styles' --public-url './' --no-hmr Currently, the HMR feature is still buggy. So, I have to disable it. With the postcss.config.js in the root directory. Parcel picks it automatically and knows how to transpile the TailwindCSS imports.
The next step is bundling the Javascript. I need to tell the alias to Parcel in package.json
"alias" : { "@modules/lazyload" : "./src/scripts/modules/lazyload/index.js" , "@utilities/selectors" : "./src/scripts/utilities/selectors/index.js" , "@utilities/helpers" : "./src/scripts/utilities/helpers/index.js" } I need to specify each one by hand. Is Parcel had something like regex for an alias? Yes, It is. But still undocumented. See The update section.
Parcel works well out of the box. It can detect changes and rebuild the site. It just works™ from the start, I don't need to read any documentation.
Snowpack My ultimate goal is to try Snowpack. Its fresh approach drags me to try it out. Mimicking other people's config, it doesn't work. After some trial, I stopped. The documentations don't help, and I only find one Eleventy project using it.
I decided to settle with Parcel as my go-to bundler for this site and my future project. The next day, I read people's blogs regarding Parcel. I wanted to know more about it. One of the articles mentioned createapp.dev. It has a Snowpack boilerplate. I give it a second chance. Using the generated boilerplate. It works!. I don't recall what's wrong with my previous config. I have deleted it from my git stash.
For the alias, I need to add:
alias : { '@modules' : './src/scripts/modules' , '@utilities' : './src/scripts/utilities' , } It is faster than the previous bundler, because it writes to the memory instead of the filesystem. The downside is you can't easily inspect the produced output. You need to inspect it using the browser developer tool.
Unfortunately, it has some serious issues. It can't detect any changes in SASS partial. I have reported the issue thoroughly but I don't get a response up until now. It also doesn't minify the Javascript code in production mode. Even with the correct config according to the docs.
Benchmark No-bundler:
❯ hyperfine --max-runs 3 'rm -rf dist && npm run prod' Benchmark #1: rm -rf dist && npm run prod Time (mean ± σ ): 5.766 s ± 0.132 s [ User: 8.289 s, System: 0.392 s ] Range (min … max ): 5.618 s … 5.871 s 3 runs Parcel:
❯ hyperfine --max-runs 3 'rm -rf dist && npm run prod' Benchmark #1: rm -rf dist && npm run prod Time (mean ± σ ): 6.452 s ± 0.139 s [ User: 9.205 s, System: 0.769 s ] Range (min … max ): 6.332 s … 6.604 s 3 runs Snowpack:
❯ hyperfine --max-runs 3 'rm -rf dist && npm run prod' Benchmark #1: rm -rf dist && npm run prod Time (mean ± σ ): 4.470 s ± 0.109 s [ User: 7.058 s, System: 0.375 s ] Range (min … max ): 4.391 s … 4.595 s 3 runs Conclusion I decided not to include Rollup because my main goal is either of the three choice below.
No-bundler is good. It tells you each small step to build the site and has less dependencies. But the integration between the tools doesn't play well.
Snowpack is faster than others, but it has some serious issue that blocks me to be productive. You can't also specify which command to run first, it will become an issue if you want to integrate a new tool.
Parcel magically works out-of-the-box, without any configuration. But you need to put any third-party config in its default location. The file path in the source code also must point to a concrete file.
In the end, I choose Parcel . It works out of the box without any serious issues. I will use it to replace the current Webpack config for this site and my future project.
Bonus I made a repository containing a configuration for no-bundler, Snowpack, and Parcel. It features Javascript modules, SASS partials, TailwindCSS, and production build requirements.
Unfortunately, with the exact same code, Snowpack doesn't work well. I got the error below:
Uncaught SyntaxError : import declarations may only appear at top level of a module I don't plan to fix it myself as I don't use Snowpack for this site anymore. Looking for the solution sometimes took hours. I am happy to accept a patch.
Update Parcel supports regex for alias, but it's undocumented. Thanks Niklas Mischkulnig for telling me about this. Now I can avoid repetition in alias:
"alias": { - "@modules/lazyload": "./src/scripts/modules/lazyload/index.js", - "@utilities/selectors": "./src/scripts/utilities/selectors/index.js", - "@utilities/helpers": "./src/scripts/utilities/helpers/index.js" + "@modules/*": "./src/scripts/modules/$1", + "@utilities/*": "./src/scripts/utilities/$1" If you liked this article, please support my work . It will definitely be rewarding and motivating. Thanks for the support!
</svg>