Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
297 changes: 291 additions & 6 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"jest": "^24.9.0",
"lint-staged": "^5.0.0",
"prettier": "^1.19.1",
"svelte-preprocess": "^5.0.0",
"ts-jest": "^26.3.0",
"ts-node": "3.3.0",
"typescript": "^3.9.7"
Expand Down
6 changes: 3 additions & 3 deletions src/commands/replace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ async function replace(
ttagOpts = Object.assign(ttagOpts, overrideOpts);
}
const babelOptions = makeBabelConf(ttagOpts);
const transformFn: TransformFn = file => {
const transformFn: TransformFn = async file => {
const relativePath = path.relative(srcPath, file);
const resultPath = path.join(out, relativePath);
const result = babel.transformFileSync(file, babelOptions);
const result = await babel.transformFileAsync(file, babelOptions);
const dir = path.dirname(resultPath);
if (dir !== ".") {
mkdirp.sync(dir);
Expand All @@ -38,7 +38,7 @@ async function replace(
progress.fail("Failed to replace");
return;
}
fs.writeFileSync(resultPath, result.code);
await fs.promises.writeFile(resultPath, result.code);
};

await pathsWalk([srcPath], progress, transformFn);
Expand Down
85 changes: 70 additions & 15 deletions src/lib/extract.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,47 @@
import "../declarations";
import * as babel from "@babel/core";
import * as fs from "fs";
import * as path from "path";
import * as tmp from "tmp";
import { pathToFileURL } from "url";
import { extname } from "path";
import { parseComponent } from "vue-sfc-parser";
import { walk } from "estree-walker";
import { parse as parseSvelte } from "svelte/compiler";
import {
parse as parseSvelte,
preprocess as preprocessSvelte
} from "svelte/compiler";
import ignore from "ignore";
import { TemplateNode } from "svelte/types/compiler/interfaces";
import { makeBabelConf } from "../defaults";
import * as ttagTypes from "../types";
import { TransformFn, pathsWalk } from "./pathsWalk";
import { mergeOpts } from "./ttagPluginOverride";

async function getSvelteConfigFile(searchDir: string): Promise<string | null> {
const filePath = path.resolve(searchDir, "svelte.config.js");
try {
const stat = await fs.promises.stat(filePath);

if (stat.isFile()) {
return filePath;
}
} catch {
// Ignored
}

const parentDir = path.resolve(searchDir, "..");
return parentDir === searchDir
? null
: await getSvelteConfigFile(parentDir);
}

// https://github.com/microsoft/TypeScript/issues/43329
async function dynamicImport(filename: string) {
const url = pathToFileURL(filename).toString();
return eval(`import("${url}")`);
}

export async function extractAll(
paths: string[],
lang: string,
Expand All @@ -33,17 +62,18 @@ export async function extractAll(
ttagOpts = mergeOpts(ttagOpts, overrideOpts);
}
const babelOptions = makeBabelConf(ttagOpts);
const transformFn: TransformFn = filepath => {
const transformFn: TransformFn = async filepath => {
try {
switch (extname(filepath)) {
case ".vue": {
const source = fs.readFileSync(filepath).toString();
const sourceBuffer = await fs.promises.readFile(filepath);
const source = sourceBuffer.toString();
const script = parseComponent(source).script;
if (script) {
const lineCount =
source.slice(0, script.start).split(/\r\n|\r|\n/)
.length - 1;
babel.transformSync(
await babel.transformAsync(
"\n".repeat(lineCount) + script.content,
{
filename: filepath,
Expand All @@ -54,8 +84,28 @@ export async function extractAll(
break;
}
case ".svelte": {
const source = fs.readFileSync(filepath).toString();
const sourceBuffer = await fs.promises.readFile(filepath);
let source = sourceBuffer.toString();
const jsCodes: string[] = [];
const configFilePath = await getSvelteConfigFile(".");
if (configFilePath) {
const config = await dynamicImport(configFilePath);
const preprocess = config?.default?.preprocess;
if (
Array.isArray(preprocess) &&
preprocess.length > 0
) {
const preprocessed = await preprocessSvelte(
source,
preprocess,
{
filename: filepath
}
);
source = preprocessed.code;
}
}

const { html, instance, module } = parseSvelte(source);

// <script> tag should include `import { t } from 'ttag'`
Expand All @@ -73,12 +123,17 @@ export async function extractAll(
}
});
}
walk(instance, {
enter(node: TemplateNode) {
if (node.type !== "Program") return;
jsCodes.push(source.slice(node.start, node.end));
}
});

if (instance) {
walk(instance, {
enter(node: TemplateNode) {
if (node.type !== "Program") return;
jsCodes.push(
source.slice(node.start, node.end)
);
}
});
}

// Collect t`...` in {...} in template
walk(html, {
Expand All @@ -99,14 +154,14 @@ export async function extractAll(
}
});

babel.transformSync(jsCodes.join("\n"), {
await babel.transformAsync(jsCodes.join("\n"), {
filename: filepath,
...babelOptions
});
break;
}
default:
babel.transformFileSync(filepath, babelOptions);
await babel.transformFileAsync(filepath, babelOptions);
}
} catch (err) {
if (err.codeFrame) {
Expand Down Expand Up @@ -143,9 +198,9 @@ function decorateTransformFn(
const ignoreFiles = rcOpts?.extractor?.ignoreFiles;
if (ignoreFiles) {
const ig = ignore().add(ignoreFiles);
return function(filename: string): void {
return async function(filename: string): Promise<void> {
if (ig.ignores(filename) === false) {
originFunc(filename);
await originFunc(filename);
}
};
}
Expand Down
15 changes: 9 additions & 6 deletions src/lib/pathsWalk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import * as path from "path";
import * as c3poTypes from "../types";
import * as fs from "fs";

export type TransformFn = (filepath: string) => void;
export type TransformFn = (filepath: string) => Promise<void>;

function walkFile(
async function walkFile(
filepath: string,
progress: c3poTypes.Progress,
transformFn: TransformFn
Expand All @@ -20,7 +20,7 @@ function walkFile(
extname === ".svelte"
) {
progress.text = filepath;
transformFn(filepath);
await transformFn(filepath);
}
}

Expand All @@ -31,8 +31,11 @@ async function walkDir(
) {
const walker = walk.walk(dirpath);
walker.on("file", (root: string, fileState: any, next: any) => {
walkFile(path.join(root, fileState.name), progress, transformFn);
next();
walkFile(
path.join(root, fileState.name),
progress,
transformFn
).then(() => next());
});
return new Promise(res => {
walker.on("end", () => res());
Expand All @@ -49,7 +52,7 @@ export async function pathsWalk(
if (fs.lstatSync(filePath).isDirectory()) {
await walkDir(filePath, progress, transformFn);
} else {
walkFile(filePath, progress, transformFn);
await walkFile(filePath, progress, transformFn);
}
})
);
Expand Down
17 changes: 17 additions & 0 deletions tests/commands/__snapshots__/test_extract.ts.snap
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`extract and preprocess from svelte 1`] = `
"msgid \\"\\"
msgstr \\"\\"
\\"Content-Type: text/plain; charset=utf-8\\\\n\\"
\\"Plural-Forms: nplurals=2; plural=(n!=1);\\\\n\\"

#: testSveltePreprocess.svelte:2
msgid \\"world\\"
msgstr \\"\\"

#: testSveltePreprocess.svelte:4
#, javascript-format
msgid \\"Hello \${ translated }!\\"
msgstr \\"\\"
"
`;

exports[`extract base case 1`] = `
"msgid \\"\\"
msgstr \\"\\"
Expand Down
16 changes: 16 additions & 0 deletions tests/commands/test_extract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,15 @@ const vuePath2 = path.resolve(
__dirname,
"../fixtures/vueTest/testVueWithTagInScript.vue"
);
const svelteFixturePath = path.resolve(__dirname, "../fixtures/svelteTest/");
const sveltePath = path.resolve(
__dirname,
"../fixtures/testSvelteParse.svelte"
);
const sveltePreprocessPath = path.resolve(
svelteFixturePath,
"testSveltePreprocess.svelte"
);
const globalFn = path.resolve(__dirname, "../fixtures/globalFunc.js");
const tsPath = path.resolve(__dirname, "../fixtures/tSParse.ts");
const tsChaning = path.resolve(__dirname, "../fixtures/tsOptionalChaning.ts");
Expand Down Expand Up @@ -65,6 +70,17 @@ test("extract from svelte", () => {
expect(result).toMatchSnapshot();
});

test("extract and preprocess from svelte", () => {
execSync(
`ts-node ../../../src/index.ts extract -o ${potPath} ${sveltePreprocessPath}`,
{
cwd: svelteFixturePath
}
);
const result = fs.readFileSync(potPath).toString();
expect(result).toMatchSnapshot();
});

test("extract from ts", () => {
execSync(`ts-node src/index.ts extract -o ${potPath} ${tsPath}`);
const result = fs.readFileSync(potPath).toString();
Expand Down
13 changes: 13 additions & 0 deletions tests/fixtures/svelteTest/svelte.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const preprocess = require("svelte-preprocess");

module.exports = {
preprocess: [
preprocess({
typescript: {
compilerOptions: {
target: "es2015"
}
}
}),
],
};
6 changes: 6 additions & 0 deletions tests/fixtures/svelteTest/testSveltePreprocess.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<script lang="ts">
import { t } from 'ttag';
export let translated: string = t`world`;
</script>

<h1>{t`Hello ${translated}!`}</h1>