mirror of
https://github.com/zhigang1992/nativewind.git
synced 2026-06-16 02:34:28 +08:00
refactor: testing compiling in metro
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "3.0.0-next.1-4-ge0c752f",
|
||||
"version": "3.0.0-next.1-5-gec33a29",
|
||||
"name": "nativewind",
|
||||
"description": "Use Tailwindcss in your cross-platform React Native applications",
|
||||
"main": "dist/index.js",
|
||||
|
||||
@@ -1,26 +1,12 @@
|
||||
import {
|
||||
readFileSync,
|
||||
writeFileSync,
|
||||
statSync,
|
||||
openSync,
|
||||
rmSync,
|
||||
} from "node:fs";
|
||||
import process from "node:process";
|
||||
import { resolve, join } from "node:path";
|
||||
|
||||
import findCacheDir from "find-cache-dir";
|
||||
import chokidar from "chokidar";
|
||||
import { resolve } from "node:path";
|
||||
|
||||
import type { ConfigAPI } from "@babel/core";
|
||||
|
||||
import type { Config } from "tailwindcss";
|
||||
import resolveConfig from "tailwindcss/resolveConfig";
|
||||
import resolveConfigPath from "tailwindcss/lib/util/resolveConfigPath";
|
||||
import { validateConfig } from "tailwindcss/lib/util/validateConfig";
|
||||
|
||||
// import { getImportBlockedComponents } from "./get-import-blocked-components";
|
||||
import { extractStyles } from "../postcss/extract";
|
||||
import { createHash } from "node:crypto";
|
||||
import { plugin } from "./plugin";
|
||||
import { normalizePath } from "./normalize-path";
|
||||
|
||||
@@ -30,95 +16,16 @@ export interface TailwindcssReactNativeBabelOptions {
|
||||
allowModuleTransform?: "*" | string[];
|
||||
blockModuleTransform?: string[];
|
||||
mode?: "compileAndTransform" | "compileOnly" | "transformOnly";
|
||||
rem?: number;
|
||||
tailwindConfigPath?: string;
|
||||
tailwindConfig?: Config | undefined;
|
||||
}
|
||||
|
||||
const cacheDirectory = findCacheDir({ name: "nativewind", create: true }) ?? "";
|
||||
if (!cacheDirectory) throw new Error("Unable to secure cache directory");
|
||||
|
||||
const stylesFile = join(cacheDirectory, "styles.js");
|
||||
const cssCacheFile = join(cacheDirectory, "styles.css");
|
||||
const masterProcessLock = join(cacheDirectory, process.ppid.toString());
|
||||
const nativewindStylesFile = require.resolve("nativewind/dist/styles");
|
||||
|
||||
export default function (
|
||||
api: ConfigAPI,
|
||||
options: TailwindcssReactNativeBabelOptions,
|
||||
cwd: string
|
||||
) {
|
||||
const [newTailwindConfig, tailwindConfigPath] = resolveTailwindConfig(
|
||||
api,
|
||||
options
|
||||
);
|
||||
let tailwindConfig = newTailwindConfig;
|
||||
|
||||
const platform = resolvePlatform(api);
|
||||
const isDevelopment = api.env("development");
|
||||
|
||||
let canCompile = true;
|
||||
let canTransform = true;
|
||||
|
||||
if (options.mode === "compileOnly") {
|
||||
canTransform = false;
|
||||
} else if (options.mode === "transformOnly") {
|
||||
canCompile = false;
|
||||
} else if (platform === "web") {
|
||||
canCompile = false;
|
||||
}
|
||||
|
||||
api.cache.using(() => {
|
||||
if (!canCompile) return true;
|
||||
|
||||
try {
|
||||
openSync(masterProcessLock, "wx");
|
||||
|
||||
for (const eventType of [
|
||||
`exit`,
|
||||
`SIGINT`,
|
||||
`SIGUSR1`,
|
||||
`SIGUSR2`,
|
||||
`uncaughtException`,
|
||||
`SIGTERM`,
|
||||
]) {
|
||||
process.on(eventType, () => {
|
||||
rmSync(masterProcessLock, { force: true });
|
||||
});
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
const watcher = chokidar.watch(cacheDirectory).on("change", (path) => {
|
||||
if (path.endsWith(".css")) {
|
||||
cssCache = readFileSync(path, "utf8");
|
||||
} else if (path === tailwindConfigPath) {
|
||||
// Reload the Tailwind Config
|
||||
tailwindConfig = resolveTailwindConfig(api, options)[0];
|
||||
fullCompile();
|
||||
}
|
||||
});
|
||||
|
||||
if (tailwindConfigPath) {
|
||||
watcher.add(tailwindConfigPath);
|
||||
}
|
||||
}
|
||||
|
||||
writeFileSync(
|
||||
nativewindStylesFile,
|
||||
`try { require("${stylesFile}") } catch {} // ${Date.now()}`
|
||||
);
|
||||
|
||||
fullCompile();
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
const safelist =
|
||||
tailwindConfig.safelist && tailwindConfig.safelist.length > 0
|
||||
? tailwindConfig.safelist
|
||||
: ["babel-empty"];
|
||||
const tailwindConfig = resolveTailwindConfig(api, options);
|
||||
|
||||
const content = Array.isArray(tailwindConfig.content)
|
||||
? tailwindConfig.content.filter(
|
||||
@@ -132,48 +39,6 @@ export default function (
|
||||
normalizePath(resolve(cwd, contentFilePath))
|
||||
);
|
||||
|
||||
let cssCache = `@tailwind components;@tailwind utilities;`;
|
||||
|
||||
function hotReloadStyles(filename: string) {
|
||||
const styles = extractStyles(
|
||||
{
|
||||
...tailwindConfig,
|
||||
content: [filename],
|
||||
safelist,
|
||||
},
|
||||
cssCache
|
||||
);
|
||||
|
||||
const hash = createHash("sha1").update(filename).digest("hex");
|
||||
const cacheFilename = join(cacheDirectory, `${hash}.js`);
|
||||
const styleString = JSON.stringify(styles);
|
||||
writeFileSync(
|
||||
cacheFilename,
|
||||
`import { NativeWindStyleSheet } from "nativewind";\nNativeWindStyleSheet.create(${styleString})`
|
||||
);
|
||||
writeFileSync(stylesFile, `try { require("${cacheFilename}"); } catch {}`, {
|
||||
flag: "a",
|
||||
});
|
||||
}
|
||||
|
||||
function fullCompile() {
|
||||
const styleString = JSON.stringify(extractStyles(tailwindConfig, cssCache));
|
||||
writeFileSync(
|
||||
stylesFile,
|
||||
`import { NativeWindStyleSheet } from "nativewind";\nNativeWindStyleSheet.create(${styleString});`
|
||||
);
|
||||
}
|
||||
|
||||
function handleCssImport(source: string) {
|
||||
const css = readFileSync(source, "utf8");
|
||||
|
||||
if (css.includes("@tailwind")) {
|
||||
cssCache = css;
|
||||
writeFileSync(cssCacheFile, cssCache);
|
||||
fullCompile();
|
||||
}
|
||||
}
|
||||
|
||||
// const allowModuleTransform = Array.isArray(options.allowModuleTransform)
|
||||
// ? ["react-native", "react-native-web", ...options.allowModuleTransform]
|
||||
// : "*";
|
||||
@@ -183,13 +48,7 @@ export default function (
|
||||
[
|
||||
plugin,
|
||||
{
|
||||
canCompile,
|
||||
canTransform,
|
||||
contentFilePaths,
|
||||
fullCompile,
|
||||
handleCssImport,
|
||||
hotReloadStyles,
|
||||
isDevelopment,
|
||||
},
|
||||
],
|
||||
],
|
||||
@@ -197,9 +56,9 @@ export default function (
|
||||
}
|
||||
|
||||
function resolveTailwindConfig(
|
||||
api: ConfigAPI,
|
||||
_: ConfigAPI,
|
||||
options: TailwindcssReactNativeBabelOptions
|
||||
): [Config, string | null] {
|
||||
): Config {
|
||||
let tailwindConfig: Config;
|
||||
|
||||
const userConfigPath = resolveConfigPath(
|
||||
@@ -209,7 +68,6 @@ function resolveTailwindConfig(
|
||||
if (userConfigPath === null) {
|
||||
tailwindConfig = resolveConfig(options.tailwindConfig);
|
||||
} else {
|
||||
api.cache.using(() => statSync(userConfigPath).mtimeMs);
|
||||
delete require.cache[require.resolve(userConfigPath)];
|
||||
const newConfig = resolveConfig(require(userConfigPath));
|
||||
tailwindConfig = validateConfig(newConfig);
|
||||
@@ -227,45 +85,5 @@ function resolveTailwindConfig(
|
||||
throw new Error("NativeWind preset was not included");
|
||||
}
|
||||
|
||||
return [tailwindConfig, userConfigPath];
|
||||
}
|
||||
|
||||
function resolvePlatform(api: ConfigAPI) {
|
||||
const bundler = api.caller((caller) => {
|
||||
if (!caller) return;
|
||||
|
||||
if ("bundler" in caller) {
|
||||
return caller["bundler"];
|
||||
}
|
||||
|
||||
const { name } = caller;
|
||||
|
||||
switch (name) {
|
||||
case "metro": {
|
||||
return "metro";
|
||||
}
|
||||
case "next-babel-turbo-loader": {
|
||||
return "webpack";
|
||||
}
|
||||
case "babel-loader": {
|
||||
return "webpack";
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const platform = api.caller((caller) => {
|
||||
if (!caller) return "unknown";
|
||||
|
||||
if ("platform" in caller) {
|
||||
return caller["platform"];
|
||||
} else if (bundler === "webpack") {
|
||||
return "web";
|
||||
} else {
|
||||
return "unknown";
|
||||
}
|
||||
});
|
||||
|
||||
process.env.NATIVEWIND_PLATFORM = platform;
|
||||
|
||||
return platform;
|
||||
return tailwindConfig;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { resolve, dirname } from "node:path";
|
||||
|
||||
import type { ConfigAPI, NodePath, PluginPass, Visitor } from "@babel/core";
|
||||
import micromatch from "micromatch";
|
||||
import { addNamed, addSideEffect } from "@babel/helper-module-imports";
|
||||
import { addNamed } from "@babel/helper-module-imports";
|
||||
|
||||
import { TailwindcssReactNativeBabelOptions } from ".";
|
||||
|
||||
@@ -29,29 +27,10 @@ import {
|
||||
import { normalizePath } from "./normalize-path";
|
||||
|
||||
export interface PluginOptions {
|
||||
canCompile: boolean;
|
||||
canTransform: boolean;
|
||||
contentFilePaths: string[];
|
||||
handleCssImport: (source: string) => void;
|
||||
fullCompile: () => void;
|
||||
isDevelopment: boolean;
|
||||
nativewindStylesFile: string;
|
||||
hotReloadStyles: (filename: string) => void;
|
||||
}
|
||||
|
||||
export function plugin(
|
||||
_: ConfigAPI,
|
||||
{
|
||||
contentFilePaths,
|
||||
canCompile,
|
||||
canTransform,
|
||||
handleCssImport,
|
||||
fullCompile,
|
||||
isDevelopment,
|
||||
hotReloadStyles,
|
||||
nativewindStylesFile,
|
||||
}: PluginOptions
|
||||
) {
|
||||
export function plugin(_: ConfigAPI, { contentFilePaths }: PluginOptions) {
|
||||
const programVisitor: Visitor<
|
||||
PluginPass & {
|
||||
opts: TailwindcssReactNativeBabelOptions;
|
||||
@@ -68,54 +47,33 @@ export function plugin(
|
||||
contentFilePaths
|
||||
);
|
||||
|
||||
if (canCompile && state.isInContent) {
|
||||
path.traverse({
|
||||
ImportDeclaration(path) {
|
||||
if (path.node.source.value.endsWith(".css")) {
|
||||
const currentDirectory = dirname(filename);
|
||||
handleCssImport(
|
||||
resolve(currentDirectory, path.node.source.value)
|
||||
);
|
||||
addSideEffect(path, `nativewind/styles`);
|
||||
path.remove();
|
||||
}
|
||||
},
|
||||
CallExpression(path) {
|
||||
const callee = path.get("callee");
|
||||
if (!callee.isIdentifier() || !callee.equals("name", "require")) {
|
||||
return;
|
||||
}
|
||||
path.traverse({
|
||||
ImportDeclaration(path) {
|
||||
if (path.node.source.value.endsWith(".css")) {
|
||||
path.remove();
|
||||
}
|
||||
},
|
||||
CallExpression(path) {
|
||||
const callee = path.get("callee");
|
||||
if (!callee.isIdentifier() || !callee.equals("name", "require")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const argument = path.get("arguments")[0];
|
||||
if (!argument || !argument.isStringLiteral()) {
|
||||
return;
|
||||
}
|
||||
const argument = path.get("arguments")[0];
|
||||
if (!argument || !argument.isStringLiteral()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (argument.node.value.endsWith(".css")) {
|
||||
const currentDirectory = dirname(filename);
|
||||
handleCssImport(resolve(currentDirectory, argument.node.value));
|
||||
addSideEffect(path, `nativewind/styles`);
|
||||
path.remove();
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
if (argument.node.value.endsWith(".css")) {
|
||||
path.remove();
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
exit(path, state) {
|
||||
if (state.didTransform) {
|
||||
addNamed(path, "StyledComponent", "nativewind");
|
||||
}
|
||||
|
||||
if (state.filename === nativewindStylesFile) {
|
||||
fullCompile();
|
||||
} else if (
|
||||
isDevelopment &&
|
||||
canCompile &&
|
||||
state.filename &&
|
||||
state.isInContent
|
||||
) {
|
||||
hotReloadStyles(state.filename);
|
||||
}
|
||||
},
|
||||
},
|
||||
JSXElement(path, state) {
|
||||
@@ -135,7 +93,6 @@ export function plugin(
|
||||
name === "_StyledComponent" || name === "StyledComponent";
|
||||
|
||||
if (
|
||||
!canTransform ||
|
||||
!someAttributes(path, ["className", "tw"]) ||
|
||||
!name ||
|
||||
isWrapper ||
|
||||
|
||||
50
packages/nativewind/src/metro/index.ts
Normal file
50
packages/nativewind/src/metro/index.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { writeFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
|
||||
import findCacheDir from "find-cache-dir";
|
||||
import { spawn } from "node:child_process";
|
||||
|
||||
export interface WithNativeWindOptions {
|
||||
inputPath?: string;
|
||||
postcssPath?: string;
|
||||
}
|
||||
|
||||
// We actually don't do anything to the Metro config,
|
||||
// this is simply here to future proof incase we need to
|
||||
export default function withNativeWind(
|
||||
config: unknown,
|
||||
{ inputPath: input, postcssPath }: WithNativeWindOptions
|
||||
) {
|
||||
const cacheDirectory = findCacheDir({ name: "nativewind", create: true });
|
||||
if (!cacheDirectory) throw new Error("Unable to secure cache directory");
|
||||
|
||||
if (!input) {
|
||||
input = join(cacheDirectory, "input.css");
|
||||
writeFileSync(input, "@tailwind components;@tailwind utilities;");
|
||||
}
|
||||
|
||||
const spawnCommands = ["tailwind", "-i", input];
|
||||
|
||||
if (postcssPath) {
|
||||
spawnCommands.push("--postcss", postcssPath);
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
spawnCommands.push("--watch");
|
||||
}
|
||||
|
||||
const cli = spawn("npx", spawnCommands);
|
||||
cli.stdout.on("data", (data) => {
|
||||
console.log(`stdout: ${data}`);
|
||||
});
|
||||
|
||||
cli.stderr.on("data", (data) => {
|
||||
console.error(`stderr: ${data}`);
|
||||
});
|
||||
|
||||
cli.on("close", (code) => {
|
||||
console.log(`child process exited with code ${code}`);
|
||||
});
|
||||
|
||||
return config;
|
||||
}
|
||||
Reference in New Issue
Block a user