I've followed this video and chrissainty/ondotnet-tailwindcss to configure Tailwind with the JIT and I'm pretty happy!
However, I wanted to take advantage of postcss-import to include multiple css files. Here's what my app.css looks like:
#import "tailwindcss/base";
#import "tailwindcss/components";
#import "tailwindcss/utilities";
#import "./other.css";
However, in the generated css I see generated code for Tailwind, but not for my other.css - it's still #import "./other.css". Here's my folder structure:
./Styles/app.css
./Styles/other.css
./package.json
./postcss.config.js
./tailwind.config.js
This is my postcss command:
cross-env TAILWIND_MODE=build postcss ./Styles/app.css -o ./wwwroot/css/app.css
I'm assuming that the problem is something to do with the Current Working Directory? I've tried the following variants for my #import statement:
#import ./Styles/other.css
#import ../Styles/other.css
#import Styles/other.css
But the text substitution doesn't happen. I've tried this with and without postcss-import in the devDependencies of the package.json.
I've included the contents of the config files, just in case.
package.json:
{
"scripts": {
"buildcss:dev": "cross-env TAILWIND_MODE=build postcss --verbose ./Styles/app.css -o ./wwwroot/css/app.css",
"buildcss:release": "cross-env NODE_ENV=production postcss ./Styles/app.css -o ./wwwroot/css/app.css"
},
"devDependencies": {
"autoprefixer": "10.3.1",
"cross-env": "7.0.3",
"cssnano": "^5.0.6",
"postcss": "8.3.6",
"postcss-cli": "8.3.1",
"postcss-import": "^14.0.2",
"tailwindcss": "2.2.7"
},
"dependencies": {
"#tailwindcss/forms": "^0.3.3"
}
}
postcss.config.js:
// postcss.config.js
const purgecss = require('#fullhuman/postcss-purgecss')({
// Specify the paths to all of the template files in your project
content: [
'./**/*.html',
'./**/*.razor'
],
// Include any special characters you're using in this regular expression
defaultExtractor: content => content.match(/[\w-/:]+(?<!:)/g) || []
})
// postcss.config.js
module.exports = {
plugins: [
require('postcss-import'),
require('tailwindcss'),
require('autoprefixer'),
...process.env.NODE_ENV === 'production'
? [purgecss]
: []
]
}
tailwind.config.js:
module.exports = {
mode: 'jit',
purge: [
'./**/*.razor',
'./**/*.cshtml'
],
darkMode: false, // or 'media' or 'class'
theme: {
extend: {
zIndex: {
'1': '1'
},
},
},
variants: {
extend: {
ringWidth: ['focus'],
borderWidth: ['focus'],
},
},
plugins: [
require('#tailwindcss/forms'),
],
}
Aha! It was the postcss config. I was looking at the wrong file. The original config was this:
module.exports = ({ env }) => ({
plugins: {
tailwindcss: {},
autoprefixer: {},
cssnano: env === "production" ? { preset: "default" } : false
}
});
It was missing the postcss-import plugin:
module.exports = ({ env }) => ({
plugins: {
tailwindcss: {},
autoprefixer: {},
'postcss-import': {},
cssnano: env === "production" ? { preset: "default" } : false
}
});
Related
Problem
I have a project let's call it root (based on Preact) that relies on a components package (based on React):
Building it using rollup works fine. When I switch components to use vitejs to build, root fails at runtime with this error:
index.es.js? [sm]:770 Uncaught TypeError: Cannot read properties of undefined (reading 'current')
at jsxDEV (index.es.js? [sm]:770:64)
at jsxWithValidation (index.es.js? [sm]:954:17)
at jsxWithValidationDynamic (index.es.js? [sm]:992:13)
at d.RecipesExplorer [as constructor] (index.es.js? [sm]:1915:33)
at d.O [as render] (index.js:532:14)
at j (index.js:190:14)
at w (children.js:137:3)
at L (index.js:418:4)
at j (index.js:246:20)
at w (children.js:137:3)
I'm not changing anything in root or how it's run. It's built using vitejs as well (in both scenarios).
Rollup config
Here's the rollup.config.js for components (this is the build scenario that works without issue):
// rollup.config.js for components
import { defineConfig } from 'rollup'
import typescript from 'rollup-plugin-typescript2'
import json from '#rollup/plugin-json'
import styles from 'rollup-plugin-styles'
export default defineConfig([
{
input: 'src/index.ts',
output: [
// I don't think we need both of these. Keeping them since we had them for now in webpack
{
dir: 'dist/esm',
format: 'es',
},
{
dir: 'dist/cjs',
format: 'cjs',
},
],
plugins: [typescript({}), json()],
external: ['react', 'react-dom', 'styled-components'],
}
])
Vitejs config
When I switch to using vitejs, this is the vite config:
// vite.config.js for components
import { defineConfig } from 'vite'
import react from '#vitejs/plugin-react'
import { resolve } from 'path'
export default defineConfig({
mode: 'development',
server: {
port: 3000,
open: false,
},
plugins: [react()],
build: {
minify: false,
target: 'modules',
outDir: './dist',
rollupOptions: {
external: ['react', 'react-dom', 'styled-components'],
},
lib: {
entry: resolve(__dirname, './src/index.ts'),
formats: ['cjs', 'es'],
fileName: format => `index.${format}.js`,
},
emptyOutDir: true,
},
})
Package.json snippet
Since vitejs and rollup place files in different directories, I change package.json as well.
For vitejs:
// package.json when configured for vitejs
"name": "components",
...
"exports": {
".": {
"import": "./dist/index.es.js",
"require": "./dist/index.cjs.js"
}
},
"scripts": {
"build": "vite build",
"build:rollup": "rollup -c",
},
...
For rollup:
// package.json when configured for rollup
"name": "components",
...
"exports": {
".": {
"import": "./dist/esm/index.js",
"require": "./dist/cjs/index.js"
}
},
"scripts": {
"build": "vite build",
"build:rollup": "rollup -c",
},
...
Command I'm running & conclusion
When I build components using npm run build (with vite), I get the above error. When I build with npm run build:rollup I get no error.
What am I doing wrong?
Appendix: Root config and package.json
My vitejs config file for root in case that's helpful. Root uses preact and components are built in React.
// vite.config.js for root
import { defineConfig } from 'vite';
import preact from '#preact/preset-vite';
import { resolve } from 'path';
const config = {
RecipeExplorer: {
entry: resolve(__dirname, './src/recipe-explorer.ts'),
fileName: 'recipe-explorer',
},
RecipeList: {
entry: resolve(__dirname, './src/recipe-list.ts'),
fileName: 'recipe-list',
},
RecipeDetailsLegacy: {
entry: resolve(__dirname, './src/recipe-details.ts'),
fileName: 'recipe-details',
},
};
/** IMPORTANT:
* We're using this LIB_NAME env variable so we can create multiple input and output files at build time.
* At the moment this is a work around. Issue is here: https://github.com/vitejs/vite/issues/4530
* Once the above issue gets resolved, we can do away with this workaround.
*/
const currentConfig = config[process.env.LIB_NAME];
if (currentConfig === undefined) {
console.warn(
'LIB_NAME is not defined or is not valid. If you are running a build command LIB_NAME must be specified.',
);
}
// https://vitejs.dev/config/
export default defineConfig({
resolve: {
alias: {
react: 'preact/compat',
'react-dom': 'preact/compat',
'react-dom/test-utils': 'preact/test-utils',
'react/jsx-runtime': 'preact/jsx-runtime',
},
},
plugins: [preact()],
server: {
port: 3010,
host: '0.0.0.0',
},
build: {
outDir: './dist',
lib: {
...currentConfig,
formats: ['cjs', 'es'],
},
emptyOutDir: false,
},
});
I run root using npm run dev which is defined as vite:
// package.json for root
"name": "root",
"scripts": {
"lint": "eslint 'src/**/*.{ts,tsx}'",
"test": "jest",
"dev": "vite",
...
}
...
I'm creating project with nx, and also use nx preset to setup the project,and I tried to add some ready-to-go UI lib, adding tailwind first, then adding 'daisyui' the tailwind components. But I got the issue that seems macro can't find daisyui class. for normal tailwind class it's working properly.
if you need more context and file config that I'm missing to show here, please tell me. thankyou for all your helps.
Error:
ERROR in ./src/app/app.tsx
Module build failed (from ../../node_modules/#nrwl/web/src/utils/web-babel-loader.js):
MacroError: path/on/my/machine/mono-repo/apps/appname/src/app/app.tsx:
✕ btn was not found
btn is button className on daisyui component.
How I use twin macro
import styled from "#emotion/styled"
import tw from "twin.macro"
...
const StyledButton = styled.button`
${tw`btn btn-primary`}
`
.babelrc
{
"presets": [
[
"#nrwl/react/babel",
{
"runtime": "automatic",
"targets": {
"browsers": [">0.25%", "not dead"]
}
}
],
"#emotion/babel-preset-css-prop",
"#babel/preset-typescript"
],
"plugins": [
"babel-plugin-transform-inline-environment-variables",
["babel-plugin-twin", { "debug": true }],
"babel-plugin-macros",
[
"#emotion/babel-plugin-jsx-pragmatic",
{
"export": "jsx",
"import": "__cssprop",
"module": "#emotion/react"
}
],
[
"#babel/plugin-transform-react-jsx",
{
"pragma": "__cssprop",
"pragmaFrag": "React.Fragment"
}
]
]
}
and project.json setup config
{
"root": "apps/appname",
"sourceRoot": "apps/appname/src",
"projectType": "application",
"targets": {
"build": {
"executor": "#nrwl/web:webpack",
"outputs": ["{options.outputPath}"],
"defaultConfiguration": "production",
"options": {
"compiler": "babel",
....others config ....
"styles": ["apps/sandbox/src/styles.scss"],
"scripts": [],
"webpackConfig": "apps/sandbox/webpackConfig.js"
},
...
}
...
}
my webpackConfig.js
const webpack = require("webpack")
const nrwlConfig = require("#nrwl/react/plugins/webpack.js")
const webpackTailwindConfig = require("./webpack-tailwind.config")
module.exports = (config, env) => {
config = nrwlConfig(config)
return {
...config,
...other config ..
module: {
...config.module,
rules: [...config.module.rules, webpackTailwindConfig.tailwindWebpackRule]
},
node: { ...config.node, global: true }
}
}
webpack-tailwind.config
const tailwindWebpackRule = {
test: /\.scss$/,
loader: "postcss-loader"
}
exports.tailwindWebpackRule = tailwindWebpackRule
and style.scss that import all tailwind
#import "tailwindcss/base";
#import "tailwindcss/components";
#import "tailwindcss/utilities";
I have the following postcss.config.js file:
// postcss.config.js
module.exports = {
plugins: [
require('postcss-import'),
require('tailwindcss'),
require('autoprefixer'),
]
}
and the following tailwind.config.js file:
// tailwind.config.js
module.exports = {
purge: [
'./src/cljs/foo/*.cljs',
'./target/cljs-runtime/*.js',
'./target/cljsbuild/public/js/*',
'./target/cljsbuild/public/js/cljs-runtime/*',
'./target/*'
],
theme: {},
variants: {},
plugins: [],
}
And my goal is to compress the css generated, for which I've added the purge key in tailwind.config.js.
To generate the css from the .src tailwind file, styles.src.css:
#tailwind base;
#tailwind components;
#tailwind utilities;
I'm running the command:
postcss ./resources/public/css/styles.src.css -o ./resources/public/css/styles.css
from the root directory of my project that contains both the tailwind.config.js and the postcss.config.js. Yet after running the command, the generated css is 1.2MB, as big as what I had without the purge key. Why isn't postcss purge working?
You don't need that command with postcss.
Just add enabled:true in purge in tailwind.config.json and wrap your path into list as stated in https://tailwindcss.com/docs/optimizing-for-production#enabling-manually:
purge: {
enabled: true,
content: [
'./src/cljs/foo/*.cljs',
'./target/cljs-runtime/*.js',
'./target/cljsbuild/public/js/*',
'./target/cljsbuild/public/js/cljs-runtime/*',
'./target/*'
],
},
There it is! Now you can run and see the results:
npm run build:css
That's the command I use in package.json:
"scripts": {
"build:css": "tailwind build static/css/tw.css -o static/css/tailwind.css"
},
Your PostCSS configuration is split between tailwind.config.js and postcss.config.js, when it should all be in postcss.config.js.
Why?
Tailwind uses PostCSS behind the scenes. But PostCSS itself doesn't know about your tailwind.config.js file. To use the postcss command, you need to specify the purge option in the postcss.config.js file, not tailwind.config.js. This page on the Tailwind website explains the difference between the two files in detail.
Here is my setup:
// postcss.config.js
module.exports = {
plugins: [
require('tailwindcss'),
require('autoprefixer'),
require('#fullhuman/postcss-purgecss')({
// Specify the paths to all of the template files in your project
content: [
'./src/cljs/foo/*.cljs',
'./target/cljs-runtime/*.js',
'./target/cljsbuild/public/js/*',
'./target/cljsbuild/public/js/cljs-runtime/*',
'./target/*'
],
// This extractor will tell PurgeCSS to ignore all CSS selectors and tags used in your files
defaultExtractor: content => Array.from(content.matchAll(/:?([A-Za-z0-9-_:]+)/g)).map(x => x[1]) || []
}),
]
}
Note my tailwind.config.js file is empty:
// tailwind.config.js
module.exports = {
purge: [],
theme: {
extend: {},
},
variants: {},
plugins: [],
}
Well you can also add purge key in postcss.config.js.
This is my config in
postcss.config.js
const purgecss = require('#fullhuman/postcss-purgecss')({
// Specify the paths to all of the template files in your project
content: ['./src/**/*.js', './public/index.html'],
// make sure css reset isnt removed on html and body
whitelist: ['html', 'body'],
// Include any special characters you're using in this regular expression
defaultExtractor: (content) => content.match(/[A-Za-z0-9-_:/]+/g) || [],
})
module.exports = {
plugins: [
require('tailwindcss'),
require('autoprefixer'),
...(process.env.NODE_ENV === 'production' ? [purgecss] : []),
],
}
Important: The environment variable NODE_ENV is responsible for dev and prod environment. If you are using tailwindcss in dev mode, then you don't want to purge as you want to use all the available styles. By setting it for production mode will inform postcss and thus this will purge unused css.
Please take note that I haven't set any config for tailwindcss in webpack config.
At build time, make sure that you have your NODE_ENV set to specific value for production use case. You can use either 'production' or 'prod' doesn't matter. Same will reflect in postcss.config.js.
Tailwind will purge automatically - from their docs:
Now whenever you compile your CSS with NODE_ENV set to production, Tailwind will automatically purge unused styles from your CSS
https://tailwindcss.com/docs/controlling-file-size#basic-usage
You can run commands for your dev and production environments - development will keep all Tailwind's classes, production will run the purge.
package.json:
"dependencies": {
"autoprefixer": "^9.8.5",
"postcss-cli": "^7.1.1",
"tailwindcss": "^1.5.2"
},
"devDependencies": {
"cross-env": "^7.0.2"
},
"scripts": {
"watch": "cross-env NODE_ENV=development postcss static/css/tailwind.css -o style.css --watch",
"build": "cross-env NODE_ENV=production postcss static/css/tailwind.css -o style.css"
},
postcss.config.js
module.exports = {
plugins: [
require('tailwindcss'),
require('autoprefixer'),
]
}
I'm having issues with jest on typescript.
//myprovider.tsx
class MyProvider{
constructor(){}
giveMeFive(): int{ return 5; }
}
export { MyProvider }
// myprovider.test.js
import { MyProvider } from './myprovider';
test('give me five!', () => {
const myprovider = new MyProvider();
/// assert
})
I get the following error
Jest encountered an unexpected token
This usually means that you are trying to import a file which Jest cannot parse, e.g. it's not plain JavaScript.
By default, if Jest sees a Babel config, it will use that to transform your files, ignoring "node_modules".
import { MyProvider } from './myprovider';
^
I'm not sure what I'm missing, I have this on my package
//package.json
"jest": {
"transform": {
".(ts|tsx)": "<rootDir>/node_modules/ts-jest/preprocessor.js"
},
"testRegex": "(/tests/.*|\\.(test|spec))\\.(ts|tsx|js)$",
"moduleFileExtensions": ["ts", "tsx", "js"]
},
// .babelrc
{
"presets": [
"#babel/preset-typescript"
]
}
//jest.config.js
module.exports = {
preset: 'ts-jest',
transform: {
'^.+\\.tsx?$': 'babel-jest',
},
}
Make sure to install these packages:
babel-jest #babel/core #babel/preset-env
Your babel config should look like this:
presets: [
[
"#babel/preset-env",
{
targets: {
node: "current",
},
},
],
"#babel/preset-typescript",
]
I've been trying to load environment variables in React and I can't seem to figure it out. I have tried multiple aproaches:
Load them using the dotenv-webpack package
webpack.config.dev.js
const merge = require('webpack-merge');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const template = require('html-webpack-template');
const Dotenv = require('dotenv-webpack');
const baseConfig = require('./webpack.config.base');
module.exports = merge(baseConfig, {
mode: 'production',
plugins: [
new HtmlWebpackPlugin({
template,
inject: false,
appMountId: 'app',
mobile: true,
lang: 'es-ES',
title: 'My App',
meta: [
{
name: 'description',
content: 'My App',
},
],
}),
new Dotenv(),
],
});
.env
API_HOST=http://localhost:8000
REACT_APP_API_HOST=http://localhost:8000
Passing it directly on the package.json script:
"start": "webpack-dev-server --config ./webpack.config.dev.js"
Using .env on the webpack command
webpack --env.API_HOST=http://localhost:8000
Using webpack.environmentPlugin
const webpack = require('webpack');
const merge = require('webpack-merge');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const template = require('html-webpack-template');
const baseConfig = require('./webpack.config.base');
module.exports = merge(baseConfig, {
mode: 'development',
devtool: 'cheap-module-source-map',
devServer: {
publicPath: '/',
contentBase: './dist',
compress: true,
stats: 'minimal',
overlay: true,
historyApiFallback: true,
port: 8081,
hot: true,
},
plugins: [
new HtmlWebpackPlugin({
template,
devServer: 'http://localhost:8081',
inject: false,
appMountId: 'app',
mobile: true,
lang: 'es-ES',
title: 'My App',
meta: [
{
name: 'description',
content: 'React template.',
},
],
}),
new webpack.EnvironmentPlugin({
API_HOST: 'http://localhost:8000',
}),
],
});
None of this approaches work and when I try to access process.env variables in my React code I get undefined
Any ideas of what I could be doing wrong?
I've been fighting with environment variables myself for some time, when I wanted to provide settings to the Firebase project but not load them into the public repository.
As far as I know, you need to name you environment variables should always start with the prefix REACT_APP_. You can define them whereever you like, but if you created your app with create-react-app tool then you can put your variables in .env file, or a few other files - (https://facebook.github.io/create-react-app/docs/adding-custom-environment-variables)
The pain for me started when I wanted to make my own files, because I had two different Firebase projects - one for staging and one for production.
I end up with using react-app-env module which helped with:
- defining my own files - staging.env and production.env
- auto prefix my variables with REACT_APP_
For example:
I have defined Firebase ApiKey in staging.env file:
API_KEY=xxxxxxxxxxxxxxxxxxx
when I use it in my firebase.js file, I use it as:
const config = {
apiKey: process.env.REACT_APP_API_KEY,
}
And to make sure that I develop against staging environment (Firebase project) I've changed my package.json to:
"scripts": {
"start": "react-app-env --env-file=staging.env start",
},
Hope that helps!
You need to specify the webpack config file correct. You will need to create a separate config for dev. (webpack.config.dev.js)
Example here.
scripts: {
"dev": "webpack --env.API_HOST=http://localhost:8000 --config webpack.config.dev.js"
}
Also, you need to use Webpack.DefinePlugin.
plugins: [
...
new webpack.DefinePlugin({ `process.env.API_HOST`: JSON.stringify(${env.API_HOST}) })
]
or you can use reduce to make it more comprehensive.
const envKeys = Object.keys(env).reduce((prev, next) => {
prev[`process.env.${next}`] = JSON.stringify(env[next]);
return prev;
}, {});
return {
plugins: [
...
new webpack.DefinePlugin(envKeys)
]
};
Agree with #Philip's answer, this is how I structure my dev.config.js
...
plugins: [
new webpack.DefinePlugin({
// process.env
'process.env': {
NODE_ENV: JSON.stringify(process.env.NODE_ENV),
WHY_DID_YOU_UPDATE: process.env.WHY_DID_YOU_UPDATE,
},
// my other global flags
__CLIENT__: true,
__SERVER__: false,
__DEVELOPMENT__: true,
__DEVTOOLS__: true
}),
]
I also use better-npm-run to manage my package.json, where you can easily manage the env variables
"betterScripts": {
"dev": {
"command": "concurrently --kill-others \"better-npm-run watch-client\" \"better-npm-run start-dev\" \"gulp watch --gulpfile semantic/gulpfile.js\""
},
"why-did-you-update": {
"command": "better-npm-run dev",
"env": {
"WHY_DID_YOU_UPDATE": true
}
},
"watch-client": {
"command": "node webpack/webpack-dev-server.js",
"env": {
"UV_THREADPOOL_SIZE": 100,
"NODE_ENV": "development",
"NODE_PATH": "./src",
"PORT": 3000
}
},
"build": {
"command": "webpack --verbose --colors --display-error-details --config webpack/prod.config.js"
}
},
Hope this information helps you too!
I find a simple solution for this. You need to install 'dotenv-webpack', then add this configuration to your webpack config:
const Dotenv = require('dotenv-webpack');
...
plugins: [
new Dotenv(),
],
...
.env
DB_HOST=127.0.0.1
DB_PASS=foobar
S3_API=mysecretkey
Finally you can access your env variables in your app
console.log(process.env.DB_PASS);
From the docs: the .env values for DB_HOST and S3_API are NOT present in our bundle, as they were never referenced (as process.env.[VAR_NAME]) in the code.
Hope it helps!
You can specify environment variables in the package.json scripts section:
{
...
"scripts": {
"start": NODE_ENV=development webpack-dev-server
},
...
}