Как построчно заменить текст в файле с помощью 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 шт):

Автор решения: vsemozhebuty

К сожалению, с 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.'); },
);
→ Ссылка
Автор решения: UserTest013

В вашем случаи 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'));
});

→ Ссылка
Автор решения: nörbörnën

Inline plugins

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>

→ Ссылка