package.json中的scripts脚本配置实战技巧与常见陷阱

书生シ玉楠 工具 阅读 2,668
赞 22 收藏
二维码
手机扫码查看
反馈

npm run build打包后index.html被覆盖的坑

今天又踩了个scripts脚本的坑,说来还挺无语的。项目打包部署的时候发现build之后index.html文件被替换成了新的,但我想保留一些自定义的meta标签和第三方脚本引用,结果每次打包都被覆盖。

package.json中的scripts脚本配置实战技巧与常见陷阱

折腾了半天发现这是个挺常见的问题,特别是当你想在HTML里加一些动态配置或者第三方统计代码的时候。

各种尝试都失败了

最开始我以为直接修改build目录下的index.html就行,结果下次打包又恢复了。然后试了各种办法,包括在package.json里改scripts命令,用postbuild钩子,甚至想通过shell脚本手动复制文件,都搞不定。

这里我踩了好几个坑:

  • 先是在build命令后面直接拼接cp命令,结果Windows和Mac兼容性有问题
  • 用cross-env也没解决跨平台问题,因为路径分隔符不一样
  • 还试过webpack插件手动注入,但配置太复杂,而且容易和其他插件冲突

折腾了两个小时,最后发现其实有几种更简单的方案。

最终解决:模板文件+构建脚本

我的方案是这样的,在public目录下创建一个index.prod.html作为生产环境模板:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8" />
    <!-- 这些是我想保留的自定义配置 -->
    <meta name="keywords" content="自定义关键词">
    <script src="https://third-party-cdn.com/tracker.js"></script>
    <!-- 其他需要保留的标签 -->
    <title>我的应用</title>
</head>
<body>
    <div id="root"></div>
    <!-- build:js 脚本会自动插入到这里 -->
</body>
</html>

然后在package.json的scripts里这么配:

{
  "scripts": {
    "build": "react-scripts build && cp public/index.prod.html build/index.html",
    "build:win": "react-scripts build && copy /Y public\index.prod.html build\index.html"
  }
}

这样就可以根据系统选择不同的构建命令。不过为了更好的跨平台支持,后来我用了更通用的方法,安装了shelljs:

npm install --save-dev shelljs

新建一个build-script.js文件:

const fs = require('fs');
const path = require('path');

// 执行build命令
require('child_process').execSync('react-scripts build', { stdio: 'inherit' });

// 复制生产环境模板
const sourcePath = path.join(__dirname, 'public', 'index.prod.html');
const targetPath = path.join(__dirname, 'build', 'index.html');

if (fs.existsSync(sourcePath)) {
  const templateContent = fs.readFileSync(sourcePath, 'utf8');
  const buildIndex = path.join(__dirname, 'build', 'index.html');
  
  if (fs.existsSync(buildIndex)) {
    // 保留原有的js/css引用
    const originalBuild = fs.readFileSync(buildIndex, 'utf8');
    
    // 将新模板的内容保留,并插入原有资源引用
    const modifiedContent = templateContent.replace(
      '<!-- build:js 脚本会自动插入到这里 -->',
      getAssetsFromOriginal(originalBuild)
    );
    
    fs.writeFileSync(buildIndex, modifiedContent);
  }
}

function getAssetsFromOriginal(content) {
  // 提取原有的CSS和JS引用
  const cssMatches = content.match(/<link rel="stylesheet"[^>]*>/g) || [];
  const jsMatches = content.match(/<script[^>]*src="[^"]*"[^>]*></script>/g) || [];
  
  return [
    ...cssMatches,
    ...jsMatches
  ].join('n    ');
}

然后把scripts改成:

{
  "scripts": {
    "build": "node build-script.js"
  }
}

还有个更优雅的方案

后来试了下react-app-rewired这个库,可以自定义webpack配置而不破坏原有配置:

npm install --save-dev react-app-rewired

根目录创建config-overrides.js:

const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');

module.exports = function override(config, env) {
  const isEnvProduction = env === 'production';
  
  if (isEnvProduction) {
    // 自定义HTML模板
    config.plugins = config.plugins.map(plugin => {
      if (plugin instanceof HtmlWebpackPlugin) {
        return new HtmlWebpackPlugin({
          inject: true,
          template: path.resolve(__dirname, 'public', 'index.prod.html'),
          minify: {
            removeComments: true,
            collapseWhitespace: true,
            removeRedundantAttributes: true,
            useShortDoctype: true,
            removeEmptyAttributes: true,
            removeStyleLinkTypeAttributes: true,
            keepClosingSlash: true,
            minifyJS: true,
            minifyCSS: true,
            minifyURLs: true,
          },
        });
      }
      return plugin;
    });
  }
  
  return config;
};

这样package.json的build命令就不用改了,依然保持原样。不过要注意,如果之前build过,可能缓存会导致配置没生效,清理一下缓存重新build就行。

线上部署的注意事项

部署到nginx服务器的时候,发现有些静态资源配置还需要注意。特别是当你的应用不在根路径下时,比如部署在 /app/ 目录下,还需要在index.prod.html里设置base标签:

<head>
    <base href="/app/" />
    <!-- 其他内容 -->
</head>

同时webpack配置里也要相应调整:

// 在config-overrides.js中
if (isEnvProduction) {
  config.output.publicPath = '/app/';
  // 其他配置...
}

还有个细节,如果用了Service Worker,记得更新sw-precache里的路径配置,不然离线缓存会出问题。

遗留的小问题

说实话这个方案还有个小问题,就是每次修改index.prod.html后,需要手动测试是否所有资源都能正常加载。特别是引入的新第三方库,有时候会出现CSP(内容安全策略)问题,需要在meta标签里调整策略。

另外,这种定制化程度比较高的情况下,团队协作时容易出现差异,最好把这套流程写成文档给组员们看。

以上是我踩坑后的总结,如果你有更好的方案欢迎评论区交流。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论