我想我看到了这个问题。@fullhuman/postcss-purgecss
从 v1.4 左右开始与 tailwindcss 捆绑在一起。由于它也是在您的 postcss 文件中手动声明的,因此它被执行了两次,这会擦除您的所有样式。此外,它仅在生产中擦除您的样式这一事实表明它是罪魁祸首,因为该特定包的执行取决于process.env.NODE_ENV===production
. 从您的 postcss 文件中删除它应该可以解决您的问题,因为它已经由 tailwindcss 包在后台执行。
这是我的postcsstailwindcss@2.0.3
用于比较:
postcss.config.js
module.exports = {
plugins: [
'postcss-import',
'tailwindcss',
'postcss-nesting',
'postcss-flexbugs-fixes',
[
'postcss-preset-env',
{
autoprefixer: {
flexbox: 'no-2009'
},
stage: 3,
features: {
'custom-properties': false,
'nesting-rules': true
}
}
]
]
};
另一种可能性是您没有将要清除的路径/文件类型包装在顶级content
对象中。从为您的tailwind.config.js
文件提供的代码中:
module.exports = {
purge: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'],
//...
}
这是我tailwind.config.js
的交叉比较
const defaultTheme = require('tailwindcss/defaultTheme');
module.exports = {
important: true,
purge: {
content: [
'./components/**/*.{js,ts,jsx,tsx}',
'./pages/**/*.{js,ts,jsx,tsx}'
],
options: {
safelist: {
standard: ['outline-none']
}
}
},
darkMode: 'class',
theme: {
lineClamp: {
1: 1,
2: 2,
3: 3,
4: 4
},
extend: {
zIndex: {
'-10': '-10',
100: '100',
150: '150'
},
maxWidth: {
'9xl': '121rem', // 1936px
'8xl': '96rem' // 1536px
},
screens: {
xs: '375px',
sm: '640px',
md: '768px',
lg: '1024px',
xl: '1280px',
'2xl': '1440px',
'3xl': '1920px'
},
transitionDuration: {
0: '0ms',
300: '300ms',
500: '500ms',
700: '700ms',
1000: '1000ms'
},
rotate: {
0: '0deg',
45: '45deg',
90: '90deg',
125: '125deg',
180: '180deg',
270: '270deg',
360: '360deg'
},
fontFamily: {
header: ['goudy-bookletter-1911', 'serif'],
poppins: ['poppins', 'sans-serif'],
somaRoman: ['neue-haas-grotesk-text', 'sans-serif'],
somaDisplay: ['neue-haas-grotesk-display', 'sans-serif'],
sans: ['Inter var', ...defaultTheme.fontFamily.sans]
},
colors: {
'reddit-0': 'var(--reddit-0)',
'reddit-1': 'var(--reddit-1)',
'reddit-2': 'var(--reddit-2)',
'reddit-3': 'var(--reddit-3)',
'reddit-4': 'var(--reddit-4)',
'primary-0': 'var(--primary-0)',
'primary-1': 'var(--primary-1)',
'primary-2': 'var(--primary-2)',
'primary-3': 'var(--primary-3)',
'primary-4': 'var(--primary-4)',
'primary-5': 'var(--primary-5)',
'primary-6': 'var(--primary-6)',
'primary-7': 'var(--primary-7)',
'primary-8': 'var(--primary-8)',
'primary-9': 'var(--primary-9)',
'secondary-0': 'var(--secondary-0)',
'secondary-1': 'var(--secondary-1)',
'secondary-2': 'var(--secondary-2)',
'accents-0': 'var(--accents-0)',
'accents-1': 'var(--accents-1)',
'accents-2': 'var(--accents-2)',
'accents-3': 'var(--accents-3)',
'accents-4': 'var(--accents-4)',
'accents-5': 'var(--accents-5)',
'accents-6': 'var(--accents-6)',
'accents-7': 'var(--accents-7)',
'accents-8': 'var(--accents-8)',
'accents-9': 'var(--accents-9)',
'theme-0': 'var(--theme-0)',
'theme-1': 'var(--theme-1)',
lightBlue: {
0: '#E3F8FF',
100: '#B3ECFF',
200: '#81DEFD',
300: '#5ED0FA',
400: '#40C3F7',
500: '#2BB0ED',
600: '#1992D4',
700: '#127FBF',
800: '#0B69A3',
900: '#035388'
},
cyan: {
0: '#E0FCFF',
100: '#BEF8FD',
200: '#87EAF2',
300: '#54D1DB',
400: '#38BEC9',
500: '#2CB1BC',
600: '#14919B',
700: '#0E7C86',
800: '#0A6C74',
900: '#044E54'
},
rojo: {
0: '#610316',
100: '#8A041A',
200: '#AB091E',
300: '#CF1124',
400: '#E12D39',
500: '#EF4E4E',
600: '#F86A6A',
700: '#FF9B9B',
800: '#FFBDBD',
900: '#FFE3E3'
},
rosado: {
0: '#620042',
100: '#870557',
200: '#A30664',
300: '#BC0A6F',
400: '#DA127D',
500: '#E8368F',
600: '#F364A2',
700: '#FF8CBA',
800: '#FFB8D2',
900: '#FFE3EC'
},
amarillo: {
0: 'hsl(15, 86%, 30%)',
100: 'hsl(22, 82%, 39%)',
200: 'hsl(29, 80%, 44%)',
300: 'hsl(36, 77%, 49%)',
400: 'hsl(42, 87%, 55%)',
500: 'hsl(44, 92%, 63%)',
600: 'hsl(48, 94%, 68%)',
700: 'hsl(48, 95%, 76%)',
800: 'hsl(48, 100%, 88%)',
900: 'hsl(49, 100%, 96%)'
},
verdeAzulado: {
// blueish-green === teal (espanol)
0: 'hsl(170, 97%, 15%)',
100: 'hsl(168, 80%, 23%)',
200: 'hsl(166, 72%, 28%)',
300: 'hsl(164, 71%, 34%)',
400: 'hsl(162, 63%, 41%)',
500: 'hsl(160, 51%, 49%)',
600: 'hsl(158, 58%, 62%)',
700: 'hsl(156, 73%, 74%)',
800: 'hsl(154, 75%, 87%)',
900: 'hsl(152, 68%, 96%)'
},
redditRed: '#FF4500',
redditNav: '#1A1A1B',
redditSearch: '#272729',
redditBG: '#141415'
},
keyframes: {
wiggle: {
'0%, 100%': { transform: 'rotate(-3deg)' },
'50%': { transform: 'rotate(3deg)' }
},
hero: {
transform: 'translate3d(0px, 0px, 0px)'
}
},
animation: {
wiggle: 'wiggle 10s ease-in-out infinite',
hero: 'hero 1s ease-in-out infinite',
slowPing: 'pulse 10s cubic-bezier(0, 0, 0.2, 1) infinite'
},
width: {
82: '20.5rem',
100: '25rem',
200: '50rem',
'8xl': '96rem'
},
height: {
75: '75vh'
},
spacing: {
7: '1.75rem',
14: '3.5rem',
18: '4.5rem',
25: '6.25rem',
26: '6.5rem',
28: '7rem',
44: '11rem',
82: '20.5rem',
100: '25rem',
104: '26rem',
156: '39rem'
},
boxShadow: {
'outline-2': '0 0 0 2px var(--accents-0)',
magical:
'rgba(0, 0, 0, 0.02) 0px 30px 30px, rgba(0, 0, 0, 0.03) 0px 0px 8px, rgba(0, 0, 0, 0.05) 0px 1px 0px',
cardHover:
'0 4px 4.1px rgba(0, 0, 0, 0.012),0 4.9px 5.8px rgba(0, 0, 0, 0.018),0 6.3px 8.4px rgba(0, 0, 0, 0.029),0 8.8px 12.9px rgba(0, 0, 0, 0.05),0 15px 23px rgba(0, 0, 0, 0.11)'
}
},
variants: {
padding: [
'responsive',
'group-hover',
'hover',
'focus',
'even',
'odd',
'first',
'last'
],
textColor: [
'responsive',
'group-hover',
'hover',
'focus',
'even',
'first',
'last',
'odd'
],
backgroundColor: [
'responsive',
'group-hover',
'hover',
'focus',
'even',
'first',
'last',
'odd'
],
display: ['responsive', 'hover', 'group-hover'],
visibility: ['responsive', 'hover', 'group-hover'],
transitionDuration: ['responsive', 'hover', 'group-hover'],
gridColumn: ['responsive', 'hover', 'first', 'odd', 'even'],
extend: {
ringWidth: [
'responsive',
'hover',
'active',
'focus',
'group-hover'
],
ringColor: [
'responsive',
'hover',
'active',
'focus',
'group-hover'
],
fontSize: ['responsive', 'last', 'first', 'hover', 'focus'],
stroke: ['responsive', 'hover', 'focus', 'group-hover'],
fill: ['responsive', 'hover', 'focus', 'group-hover'],
gridTemplateColumns: [
'responsive',
'last',
'first',
'hover',
'focus'
],
animation: [
'responsive',
'hover',
'focus',
'motion-safe',
'motion-reduce'
],
transitionProperty: [
'responsive',
'hover',
'focus',
'motion-safe',
'motion-reduce'
],
transitionDuration: ['responsive', 'hover', 'focus'],
transitionTimingFunction: ['responsive', 'hover', 'focus'],
transitionDelay: ['responsive', 'hover', 'focus'],
scale: [
'responsive',
'hover',
'focus',
'active',
'group-hover'
],
rotate: [
'responsive',
'hover',
'focus',
'active',
'group-hover'
]
}
}
},
plugins: [
require('tailwindcss-line-clamp'),
require('@tailwindcss/typography'),
require('@tailwindcss/forms'),
require('@tailwindcss/aspect-ratio')
]
};
如果你想知道为什么我postcss-import
之前tailwindcss
在 postcss.config.js 中使用了这个包,它是为了分离关注点和更简洁的代码。我有一个包含、、、和的顶级styles
目录。它们的内容如下:index.css
base.css
utilities.css
components.css
chrome-bug.css
index.css
@import 'tailwindcss/base';
@import './base.css';
@import 'tailwindcss/components';
@import './components.css';
@import 'tailwindcss/utilities';
@import './utilities.css';
components.css
.fit {
min-height: calc(100vh - 88px);
}
utilities.css
#tsparticles {
position: fixed;
width: 100%;
height: 100%;
top: 0;
left: 0;
margin: 0;
}
.skeleton {
display: block;
width: 100%;
border-radius: 5px;
background-image: linear-gradient(
270deg,
var(--accents-1),
var(--accents-2),
var(--accents-2),
var(--accents-1)
);
background-size: 400% 100%;
animation: loading 8s ease-in-out infinite;
}
@keyframes loading {
0% {
background-position: 200% 0;
}
100% {
background-position: -200% 0;
}
}
base.css
#__next {
display: flex;
flex-direction: column;
min-height: 100vh;
}
:root {
--reddit-0: hsl(240, 2%, 8%);
--reddit-1: hsl(240, 2%, 10%);
--reddit-2: hsl(240, 3%, 16%);
--reddit-3: hsl(16, 100%, 50%);
--primary-0: hsl(209, 61%, 16%);
--primary-1: hsl(211, 39%, 23%);
--primary-2: hsl(209, 34%, 30%);
--primary-3: hsl(209, 28%, 39%);
--primary-4: hsl(210, 22%, 49%);
--primary-5: hsl(209, 23%, 60%);
--primary-6: hsl(211, 27%, 70%);
--primary-7: hsl(210, 31%, 80%);
--primary-8: hsl(212, 33%, 89%);
--primary-9: hsl(210, 36%, 96%);
--secondary-0: #d7be69;
--secondary-1: #486581;
--secondary-2: #9fb3c8;
--accents-0: hsl(195, 7%, 11%);
--accents-1: hsl(140, 2%, 26%);
--accents-2: hsl(0, 0%, 49%);
--accents-3: hsl(0, 0%, 64%);
--accents-4: hsl(0, 1%, 81%);
--accents-5: hsl(0, 0%, 89%);
--accents-6: hsl(50, 21%, 95%);
--theme-0: hsl(210, 24%, 84%);
--theme-1: hsl(209, 28%, 39%);
@apply overflow-x-hidden;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
/* Remove Safari input shadow on mobile */
textarea,
input:matches([type='email'], [type='number'], [type='password'], [type='search'], [type='tel'], [type='text'], [type='url']) {
-webkit-appearance: none;
}
html {
height: 100%;
box-sizing: border-box;
touch-action: manipulation;
font-feature-settings: 'case' 1, 'rlig' 1, 'calt' 0;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-webkit-tap-highlight-color: transparent;
-moz-osx-font-smoothing: grayscale;
--webkit-text-size-adjust: none;
text-size-adjust: none;
scroll-behavior: smooth;
}
html,
body {
font-family: var(--font-sans);
text-rendering: optimizeLegibility;
::--webkit-font-smoothing: antialiased;
::--moz-osx-font-smoothing: grayscale;
background-color: var(--reddit-0);
color: var(--text-accents-6);
}
body {
position: relative;
min-height: 100%;
margin: 0;
scrollbar-width: none;
scrollbar-color: var(--primary-0) var(--primary-9); /* scroll thumb and track */
}
body::-webkit-scrollbar {
display: thin; /* Hide scrollbar for Chrome, Safari and Opera https://www.w3schools.com/howto/howto_css_hide_scrollbars.asp */
width: 10px;
}
body::-webkit-scrollbar-track {
background: var(--accents-7); /* color of the tracking area */
}
body::-webkit-scrollbar-thumb {
background-color: var(
--secondary-0
); /* color of the scroll thumb */
border-radius: 0px; /* roundness of the scroll thumb */
border: 3px var(--secondary-0); /* creates padding around scroll thumb */
}
a {
-webkit-tap-highlight-color: hsla(0, 0%, 0%, 0);
}
.animated {
--webkit-animation-duration: 1s;
--animation-duration: 1s;
-animation-duration: 1s;
--webkit-animation-fill-mode: both;
animation-fill-mode: both;
}
.fadeIn {
-webkit-animation-name: fadeIn;
animation-name: fadeIn;
}
@-webkit-keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
chrome-bug.css
/**
* Chrome has a bug with transitions onLoad since 2012!
*
* To prevent a "pop" of content, you have to disable all transitions until
* the page is done loading.
*
* https://lab.laukstein.com/bug/input
* https://twitter.com/timer150/status/1345217126680899584
*/
body.loading * {
transition: none !important;
}
chrome-bug 类通过useEffect
钩子有条件地处理,_app.tsx
并在_document.tsx
_app.tsx
import '@/styles/index.css';
import '@/styles/chrome-bug.css';
import 'keen-slider/keen-slider.min.css';
import App, {
AppContext,
AppInitialProps,
AppProps,
NextWebVitalsMetric
} from 'next/app';
import { useRouter } from 'next/router';
import { ApolloProvider } from '@apollo/client';
import { useEffect, FC } from 'react';
import {
useApollo,
initializeApollo,
addApolloState
} from '@/lib/apollo';
import * as gtag from '@/lib/analytics';
import { MediaContextProvider } from '@/lib/artsy-fresnel';
import { Head } from '@/components/Head';
import { GTagPageview } from '@/types/analytics';
// import { AppLayout } from '@/components/Layout';
import {
DynamicNavQuery,
DynamicNavQueryVariables,
DynamicNavDocument,
MenuNodeIdTypeEnum
} from '@/graphql/generated/graphql';
const Noop: FC = ({ children }) => <>{children}</>;
function NextApp({
Component,
pageProps: { ...pageProps }
}: AppProps<typeof NextApp.getInitialProps>) {
const apolloClient = useApollo(pageProps);
const LayoutNoop = (Component as any).LayoutNoop || Noop;
const router = useRouter();
useEffect(() => {
document.body.classList?.remove('loading');
}, []);
useEffect(() => {
const handleRouteChange = (url: GTagPageview) => {
gtag.pageview(url);
};
router.events.on('routeChangeComplete', handleRouteChange);
return () => {
router.events.off('routeChangeComplete', handleRouteChange);
};
}, [router.events]);
return (
<>
<Head />
<ApolloProvider client={apolloClient}>
<MediaContextProvider>
<LayoutNoop pageProps={pageProps}>
<Component {...pageProps} />
</LayoutNoop>
</MediaContextProvider>
</ApolloProvider>
</>
);
}
NextApp.getInitialProps = async (
appContext: AppContext
): Promise<AppInitialProps> => {
const pageProps = await App.getInitialProps(appContext);
const graphqlClient = initializeApollo();
const dynamicNav = await graphqlClient.query<
DynamicNavQuery,
DynamicNavQueryVariables
>({
query: DynamicNavDocument,
variables: {
idHead: 'Header',
idTypeHead: MenuNodeIdTypeEnum.Name,
idFoot: 'Footer',
idTypeFoot: MenuNodeIdTypeEnum.Name
}
});
return addApolloState(graphqlClient, {
pageProps: {
...pageProps.pageProps,
Header: dynamicNav.data.Header,
Footer: dynamicNav.data.Footer
}
});
};
export function reportWebVitals(metric: NextWebVitalsMetric) {
console.debug('vital: ', metric);
}
export default NextApp;
_document.tsx
import Document, {
Head,
Html,
Main,
NextScript,
DocumentContext
} from 'next/document';
import { mediaStyles } from '@/lib/artsy-fresnel';
const GA_TRACKING_ID = 'G-RJQZB1C7TR';
class MyDocument extends Document {
static async getInitialProps(ctx: DocumentContext) {
const initialProps = await Document.getInitialProps(ctx);
return { ...initialProps };
}
render() {
return (
<Html lang='en-US'>
<Head>
<meta charSet='utf-8' />
<link
rel='stylesheet'
href='https://rsms.me/inter/inter.css'
/>
<link rel='shortcut icon' href='/assets/favicon.ico' />
<style
type='text/css'
dangerouslySetInnerHTML={{ __html: mediaStyles }}
/>
<script
async
src={`https://www.googletagmanager.com/gtag/js?id=${GA_TRACKING_ID}`}
/>
<script
dangerouslySetInnerHTML={{
__html: `
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${GA_TRACKING_ID}', {
page_path: window.location.pathname,
});
`
}}
/>
</Head>
<body className='loading'>
<Main />
<NextScript />
</body>
</Html>
);
}
}
export default MyDocument;
为了彻底起见,这是我的package.json
{
"name": "clean-fade",
"version": "1.0.0",
"main": "index.js",
"repository": "git@gitlab.com:windy-city-devs-llc/clean-fade.git",
"author": "Andrew Ross <andrew.simpson.ross@gmail.com>",
"license": "MIT",
"scripts": {
"test": "jest",
"test-all": "yarn lint && yarn type-check && yarn test",
"codegen": "graphql-codegen --config codegen.yml -r dotenv/config",
"dev": "next -p 5005",
"prod:build": "yarn codegen && yarn build",
"dev:debug": "cross-env NODE_OPTIONS='--inspect' next dev",
"build": "next build",
"analyze": "cross-env ANALYZE=true yarn build",
"find:unused": "next-unused",
"tsconfig:effective": "tsc --showConfig",
"pretty:quick": "yarn pretty-quick --staged",
"prepare": "husky install",
"lint": "eslint . --ext ts --ext tsx --ext js",
"format": "prettier --write .",
"type-check": "tsc --pretty --noEmit"
},
"lint-staged": {
"*.@(ts|tsx)": [
"yarn lint",
"yarn format"
]
},
"next-unused": {
"alias": {
"@/components/*": [
"components/*"
],
"@/config/*": [
"config/*"
],
"@/graphql/*": [
"graphql/*"
],
"@/lib/*": [
"lib/*"
],
"@/pages/*": [
"pages/*"
],
"@/scripts/*": [
"scripts/*"
],
"@/styles/*": [
"styles/*"
],
"@/test/*": [
"test/*"
],
"@/types/*": [
"types/*"
]
},
"debug": true,
"include": [
"components",
"lib",
"pages"
],
"exclude": [],
"entrypoints": [
"pages"
]
},
"dependencies": {
"@apollo/client": "^3.3.12",
"@artsy/fresnel": "^1.3.1",
"@headlessui/react": "^0.3.1",
"@reach/portal": "^0.13.2",
"body-scroll-lock": "^3.1.5",
"classnames": "^2.2.6",
"date-fns": "^2.19.0",
"graphql": "^15.5.0",
"html-react-parser": "^1.2.4",
"isomorphic-unfetch": "^3.1.0",
"js-cookie": "^2.2.1",
"keen-slider": "^5.4.0",
"lodash.random": "^3.2.0",
"lodash.throttle": "^4.1.1",
"next": "^10.0.9",
"next-seo": "^4.20.0",
"next-themes": "^0.0.12",
"preact": "^10.5.13",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-intersection-observer": "^8.31.0",
"react-markdown": "^5.0.3",
"react-merge-refs": "^1.1.0",
"react-tsparticles": "^1.20.1",
"remark-gfm": "^1.0.0",
"swr": "^0.5.3",
"tabbable": "^5.1.6"
},
"devDependencies": {
"@babel/core": "^7.13.10",
"@graphql-codegen/cli": "^1.21.3",
"@graphql-codegen/import-types-preset": "^1.18.1",
"@graphql-codegen/introspection": "^1.18.1",
"@graphql-codegen/near-operation-file-preset": "^1.17.13",
"@graphql-codegen/schema-ast": "^1.18.1",
"@graphql-codegen/typescript": "^1.21.1",
"@graphql-codegen/typescript-operations": "^1.17.15",
"@graphql-codegen/typescript-react-apollo": "2.1.1",
"@next/bundle-analyzer": "^10.0.9",
"@tailwindcss/aspect-ratio": "^0.2.0",
"@tailwindcss/forms": "^0.2.1",
"@tailwindcss/typography": "^0.4.0",
"@testing-library/dom": "^7.30.0",
"@testing-library/jest-dom": "^5.11.9",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^12.8.3",
"@types/body-scroll-lock": "^2.6.1",
"@types/classnames": "^2.2.11",
"@types/gtag.js": "^0.0.4",
"@types/jest": "^26.0.20",
"@types/js-cookie": "^2.2.6",
"@types/lodash.random": "^3.2.6",
"@types/lodash.throttle": "^4.1.6",
"@types/node": "^14.14.35",
"@types/react": "^17.0.3",
"@types/react-dom": "^17.0.2",
"@types/react-test-renderer": "^17.0.1",
"@types/tabbable": "^3.1.0",
"@typescript-eslint/eslint-plugin": "^4.18.0",
"@typescript-eslint/parser": "^4.18.0",
"autoprefixer": "^10.2.5",
"babel-jest": "^26.6.3",
"cross-env": "^7.0.3",
"dotenv": "^8.2.0",
"dotenv-cli": "^4.0.0",
"eslint": "^7.22.0",
"eslint-config-prettier": "^8.1.0",
"eslint-plugin-react": "^7.22.0",
"globby": "^11.0.2",
"husky": "^5.1.3",
"identity-obj-proxy": "^3.0.0",
"jest": "^26.6.3",
"jest-watch-typeahead": "^0.6.1",
"lint-staged": "^10.5.4",
"next-page-tester": "^0.24.0",
"next-unused": "^0.0.3",
"postcss": "^8.2.8",
"postcss-flexbugs-fixes": "^5.0.2",
"postcss-import": "^14.0.0",
"postcss-preset-env": "^6.7.0",
"postinstall-postinstall": "^2.1.0",
"prettier": "^2.2.1",
"pretty-quick": "^3.1.0",
"react-test-renderer": "^17.0.1",
"stylelint": "^13.12.0",
"stylelint-config-recommended": "^4.0.0",
"tailwindcss": "^2.0.3",
"tailwindcss-line-clamp": "^1.0.5",
"ts-jest": "^26.5.3",
"typescript": "^4.2.3",
"yaml-loader": "^0.6.0"
}
}