Как построчно заменить текст в файле с помощью writeStream.write?
Есть файл:
<!DOCTYPE html>
<html lang="en">
someText
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="css/style.min.css">
<title>Document</title>
</head>
<body class="wrapper">
<main class="page">
</main>
</div>
</body>
<script type="text/javascript" src="js/script.min.js"></script>
</html>
В нем с помощью nodejs и gulp хочу заменить строки .*?<script.* и .*?<link.* на подключения стилей и скриптов wordpress. Делаю с помощью fs.writeStream так как он позволяет записывать в файл построчно. Вот код:
gulp.task(`wp`, () => {
let filepathes = [];
return src(project.src.html) // открыли файл
.pipe(rename({ extname: `.php` }))
.pipe(dest(project.build.php))
.pipe(transfob((file, enc, next) => {
fs.readFile(file.path, 'utf8', (err, doc) => {
if (err) return console.log(err);
filepathes = file.contents.toString().match(/([`"'])\S+?\.(css|js)\1/gi); // нашли пути к скриптам и стилям
console.log(filepathes);
let repleceStrings = file.contents.toString().match(/.*?<(link|script).*/gi);
let fileStream = fs.createWriteStream(file.path, {flags: `a`, encoding: "utf-8"});
for (let i in repleceStrings) {
let result = '';
if (filepathes[i].match(/\.js/)) result = doc.replace(repleceStrings[i], `\twp_enque_script(${filepathes[i]});\r\n`); // формируем строки со скриптами
else if (filepathes[i].match(/\.css/)) result = doc.replace(repleceStrings[i],`\twp_enque_style(${filepathes[i]});\r\n`); // со стилями
fileStream.once("open", (fd) => { fileStream.write(result);}) // пишем строку в файл
}
});
next(null, file);
}))
}
);
Проблема в том, что на выходе я получаю такой файл:
<!DOCTYPE html>
<html lang="en">
someText
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="css/style.min.css">
<title>Document</title>
</head>
<body class="wrapper">
<main class="page">
</main>
</div>
</body>
<script type="text/javascript" src="js/script.min.js"></script>
</html><!DOCTYPE html>
<html lang="en">
someText
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
wp_enque_style("css/style.min.css");
<title>Document</title>
</head>
<body class="wrapper">
<main class="page">
</main>
</div>
</body>
<script type="text/javascript" src="js/script.min.js"></script>
</html><!DOCTYPE html>
<html lang="en">
someText
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="css/style.min.css">
<title>Document</title>
</head>
<body class="wrapper">
<main class="page">
</main>
</div>
</body>
wp_enque_script("js/script.min.js");
</html>
Объясните, почему все его содержимое дублируется трижды? Как все-таки писать в файл построчно?
Ответы (3 шт):
К сожалению, с gulp не знаком, поэтому могу предложить только чистый нодовский код для адаптации. Один из возможных вариантов, если нужно использование потоков:
const fs = require('fs');
const readline = require('readline');
const { pipeline } = require('stream');
async function* processLineByLine(path) {
const fileStream = fs.createReadStream(path);
const rl = readline.createInterface({
input: fileStream,
crlfDelay: Infinity
});
for await (const line of rl) {
// Do something with the line.
yield `${line}\n`;
}
}
pipeline(
processLineByLine('test.txt'),
fs.createWriteStream('test2.txt'),
() => { console.log('Done.'); },
);
В вашем случаи fs использовать не нужно вообще так как gulp имеет функционал под названием vinyl-fs. Он уже вычитал все файлы и работает с их контентом. Следующий код обрабатывает файл с вашего вопроса. Но используйте его только как пример потому что я мог налажать с регулярками. В переменной contents находится все содержимое файла, с ней можно работать и построчно и как с целым файлом.
const gulp = require('gulp');
const rename = require('gulp-rename');
const transform = require('gulp-transform');
gulp.task('default', () => {
return gulp
.src('./*.html')
.pipe(rename({
extname: '.php'
}))
.pipe(transform('utf8', (contents) => (contents
.replace(/(<script type="text\/javascript" src=")(.+"><\/script>)/gi, '$1wp_enque_script/$2')
.replace(/(<link rel="stylesheet" type="text\/css" href=")(.+">)/gi, '$1wp_enque_style/$2')
)))
.pipe(gulp.dest('./output'));
});
Inline plugins are one-off Transform Streams you define inside your gulpfile by writing the desired behavior.
const path = require('path');
const { src, dest } = require('gulp');
const rename = require('gulp-rename');
const through2 = require('through2');
const project = {
src: {
html: `${__dirname}/*.html`,
},
build: {
php: path.join(__dirname, 'php'),
}
};
src(project.src.html)
.pipe(rename({ extname: `.php` }))
.pipe(through2.obj(function(file, enc, cb) {
if (file.isBuffer()) {
let raw = file.contents.toString(enc);
raw = raw.replace(/(?<=\<link.+?href=["'])(.+)(?=['"].*?>)/g, "wp_enque_style('$1')");
raw = raw.replace(/(?<=\<script.+?src=["'])(.+)(?=['"].*?>)/g, "wp_enque_script('$1')");
file.contents = Buffer.from(raw);
}
cb(null, file);
}))
.pipe(dest(project.build.php));
Результат:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="wp_enque_style('css/style.min.css')">
<title>Document</title>
</head>
<body class="wrapper">
<main class="page">
</main>
</div>
</body>
<script type="text/javascript" src="wp_enque_script('js/script.min.js')"></script>
</html>