4

我们有一个 Ionic 2 应用程序,它可以本地部署,也可以部署到网络上。构建时我使用npm run build --prod --release. 那只是包装ionic build.

我正在尝试更新我们的构建过程,以便能够换出main.js.index.html 中的默认值

所以我希望能够从以下位置更改此文件:

<script src="build/main.js"></script>

with(自动生成的哈希)

<script src="build/main.7b297e8f7d1c2760a1bc.js"></script>

第一步是生成文件。通过使用 webpack output.filename 设置,我能够在每次构建时成功生成正确的文件。

module.exports = {
   entry: [process.env.IONIC_APP_ENTRY_POINT, './web.config', './src/ai.min.js'],
   output: {
    path: '{{BUILD}}',
    filename: '[name].[chunkhash].js',

当我构建时,我可以看到它正在正确生成源文件,但是在完成失败后不久,ionic build出现一条消息说它找不到build/main.js. 那是原始文件名,所以我想我需要以某种方式让 ionic 知道我正在更改名称main.js 文件。

错误:

[11:00:32] build prod failed: ENOENT: no such file or directory, open '/Users/work/client/www/build/main.js' [11:00:32] ionic-app-script task: "build" [11:00:32] Error: ENOENT: no such file or directory, open '/Users/work/client/www/build/main.js'

我不确定如何更新ionic build,以便它知道查找动态生成的 main.js 文件名。

4

3 回答 3

4

我从一个离子论坛线程(https://forum.ionicframework.com/t/file-revisions/75028/2,由 aszmyd 提供)中找到了解决此问题的更好解决方案,该线程解决了散列 main.css 文件名的问题也是。我对脚本做了一些小调整,因为我没有 oauth.html 或 kajam.js。

该解决方案的优点是它不会尝试拦截离子构建,而只会处理结果。

(毫无疑问,有人会发现我发布此内容的方式有问题,但它对我非常有用,我希望对其他人有用。我无法想象有一个 Web 应用程序没有对所有 css 和 js 文件进行完整的缓存清除。 )

要运行它,只需添加:

node <the-file-name.js>

在您的构建中,离子脚本构建完成后。

#!/usr/bin/env node
'use strict';

var md5File = require('md5-file'),
    fs = require('fs');

/**
 * This script renames files inside platforms/browser/www/ folder and updates their references in html files like index.html
 * The mechanism is for improve caching. So file like `main.js` will be renamed to `main.[FILE-MD5-HASH].js` and its references
 * in html files will be updated.
 */
var buildFolder = 'www/';
var assetsFolder = buildFolder + 'build/';

var jsFiles = [
    'main'
];
var cssFiles = [
    'main'
];
var htmlFilesToUpdate = [
    'index.html'
];
var replacements = [];

jsFiles.forEach(function (file) {
    var hash = md5File.sync(assetsFolder + file + '.js');
    renameFile(file + '.js', file + '.' + hash + '.js');
});

cssFiles.forEach(function (file) {
    var hash = md5File.sync(assetsFolder + file + '.css');
    renameFile(file + '.css', file + '.' + hash + '.css');
});
htmlFilesToUpdate.forEach(function (htmlFile) {
    console.log('Update "' + htmlFile + '" with new file revisions.');
    console.log('Replacements: ' + JSON.stringify(replacements));
    replacements.forEach(function (replacementObject) {
        replaceInFile(buildFolder + htmlFile, replacementObject.from, replacementObject.to);
    });
});

function renameFile(input, output) {
    console.log('Rename "' + input + '" to "' + output + '"');
    fs.rename(assetsFolder + input, assetsFolder + output);
    if (fs.existsSync(assetsFolder + input + '.map')) {
        console.log('Rename "' + input + '.map" to "' + output + '.map"');
        fs.rename(assetsFolder + input + '.map', assetsFolder + output + '.map');
    }
    replacements.push({from: input, to: output});
}

function replaceInFile(file, regex, replacement) {
    var fileContents = fs.readFileSync(file, 'utf-8');
    fs.writeFileSync(file, fileContents.replace(regex, replacement), 'utf8');
}
于 2017-07-12T18:55:10.787 回答
4

编辑

当 ionic 有重大更新时,更简单的解决方案不太可能中断。

https://gist.github.com/haydenbr/7df417a8678efc404c820c61b6ffdd24


所以用离子缓存破坏。这是一个骇人听闻的解决方案,但现在可以使用。问题是离子构建系统有时抽象得太多了。早在 10 月,有人问是否有办法实现缓存清除。ionic 团队回应称,未来可能会考虑,但此后一直没有任何活动。这是github问题

所以我将展示对 webpack config 和 package.json 的所有更改,然后解释所有内容。

package.json 的配置部分应如下所示。

  "config": {
    "ionic_webpack": "./webpack.config.js",
    "ionic_source_map_type": "source-map",
    "ionic_uglifyjs": "./www/uglifyjs.config.json"
  }

对于您的 webpack 配置,您的输入和输出可以保持不变。确保您需要以下模块,然后您需要添加以下插件:

var path = require('path'),
    fs = require('fs'),
    ManifestPlugin = require('webpack-manifest-plugin'),
    HtmlWebpackPlugin = require('html-webpack-plugin');

...

plugins: [
  new HtmlWebpackPlugin({
    filename: './../index.html',
    inject: 'body',
    template: './src/index.html',
    title: 'My App',
  }),
  new ManifestPlugin(),
  updateFileName
]

updateFileName如下_

function updateFileName() {
  this.plugin("done", function() {
  var manifest = require(process.env.IONIC_BUILD_DIR + '/manifest.json'),
      fileName = process.env.IONIC_OUTPUT_JS_FILE_NAME;

    updateUglifyConfig(fileName, manifest);

    process.env.IONIC_OUTPUT_JS_FILE_NAME = manifest[fileName];
  });
}

function updateUglifyConfig(fileName, manifest) {
  var uglifyConfig = {
    sourceFile: manifest[fileName],
    destFileName: manifest[fileName],
    inSourceMap: manifest[fileName + '.map'],
    outSourceMap: manifest[fileName + '.map'],
    mangle: true,
    compress: true,
    comments: true
  };

  // we're writing this to www because it's specific to the current
  // build and we don't want to commit it
  fs.writeFileSync(
    path.join(__dirname, 'www', 'uglifyjs.config.json'),
    JSON.stringify(uglifyConfig, null, 4)
  );
}

那么这里到底发生了什么?首先,在 package.json 中,我们将不得不为 ionic 构建过程生成一个新的 uglify 配置。您可以在构建过程中更改文件名,只要将新名称分配给,process.env.IONIC_OUTPUT_JS_FILE_NAME那么构建的其余部分就可以正常工作,除了 uglify 步骤仍将查找默认名称main.js. 我们将在下面看到如何生成它。

现在对于我们要添加的三个插件。

第一个有一些魔力。它真的是可配置的。它是如何设置的,它需要一个默认的 index.html,设置标题,<script>为 javascript 输出注入一个标签,然后将它写入你在 filename 属性中指定的位置。如果您使用的是来自 ionic starter 应用程序的默认 index.html,那么您需要做的就是摆脱<script src="build/main.js"></script>它,它会自动将新链接放入其中包含哈希的文件名中。文档在这里

下一个插件为我们输出一个清单文件,这样我们就可以知道文件名是什么和哈希。默认情况下,它以www/build/. 文档在这里

下一个插件将新文件名分配给 process.env.IONIC_OUTPUT_JS_FILE_NAME 并为我们生成新的 uglify 配置。几乎我们抓取了输出的清单,将一个新的 uglify 配置写入 www 目录,然后根据我们从清单中得到的新文件名分配新的文件名。

差不多就是这样。如果您不希望 dev 的缓存破坏,请保留 html 插件,摆脱其他两个,然后将输出文件名更改回process.env.IONIC_OUTPUT_JS_FILE_NAME. 如果你这样做,你根本不需要引用你的主 js 文件src/index.html。无论您正在运行的开发人员还是产品,它都会被放入。有关在不同环境中使用不同 webpack 设置的更多信息,请查看

更新:

对于离子 3:

  1. 确保您的 中具有以下compilerOptions设置tsconfig

"module": "es2015", "target": "es5"

  1. npm i cheerio --save-dev
  2. 在你的 webpack 配置中添加var cheerio = require('cheerio')
  3. 摆脱 Webpack Manifest 插件。
  4. 更改updateFileName为以下内容:

    function updateFileName() {
      this.plugin("done", function(stats) {
        var buildOutput = stats.toJson()['assetsByChunkName']['main'],
            fileName = process.env.IONIC_OUTPUT_JS_FILE_NAME,
            manifest = {
              [fileName]: buildOutput[0],
              [fileName + '.map']: buildOutput[1]
            };
    
        updateUglifyConfig(fileName, manifest);
    
        process.env.IONIC_OUTPUT_JS_FILE_NAME = manifest[fileName];
        console.log('IONIC_OUTPUT_JS_FILE_NAME', process.env.IONIC_OUTPUT_JS_FILE_NAME);
      });
    }
    
  5. 摆脱 Html Webpack 插件

  6. 代替 html 插件,将以下函数放入 webpack 配置的 plugins 数组中:

    function updateIndexHTML() {
    this.plugin("done", function(stats) {
    var buildOutput = stats.toJson()['assetsByChunkName']['main'],
        outputFileName = buildOutput[0],
        currentIndexHTML = fs.readFileSync(
          path.join(__dirname, 'src', 'index.html'),
          { encoding: 'utf8' }
        ),
        $ = cheerio.load(currentIndexHTML);
    
    $('body').append(`<script src="build/${outputFileName}"></script>`);
    
    fs.writeFileSync(
      path.join(process.env.IONIC_WWW_DIR, 'index.html'),
      $.html()
    );
      });
    }
    
于 2017-04-15T17:53:41.493 回答
0

解决方案与 Ionic 2.x 和 3.x 完美配合

#!/usr/bin/env node

var fs = require('fs'),
    path = require('path'),
    cheerio = require('cheerio'),
    revHash = require('rev-hash');

/**
 *
 * @param string fileName
 * @returns string
 */
function hashFile(file) {

    // Get file name
    var fileName = file.replace(/\.[^/.]+$/, "");
    // Get file extension
    var re = /(?:\.([^.]+))?$/;
    var fileExtension = re.exec(file)[1];

    var filePath = path.join(buildDir, file);
    var fileHash = revHash(fs.readFileSync(filePath));
    var fileNewName = `${fileName}.${fileHash}.${fileExtension}`;
    var fileNewPath = path.join(buildDir, fileNewName);
    var fileNewRelativePath = path.join('build', fileNewName);
    //Rename file
    console.log("cache-busting.js:hashFile:Renaming " + filePath + " to " + fileNewPath);
    fs.renameSync(filePath, fileNewPath);

    return fileNewRelativePath;
}


var rootDir = path.resolve(__dirname);
var wwwRootDir = path.resolve(rootDir, 'www');
var buildDir = path.join(wwwRootDir, 'build');
var indexPath = path.join(wwwRootDir, 'index.html');
$ = cheerio.load(fs.readFileSync(indexPath, 'utf-8'));

$('head link[href="build/main.css"]').attr('href', hashFile('main.css'));
$('body script[src="build/main.js"]').attr('src', hashFile('main.js'));
$('body script[src="build/polyfills.js"]').attr('src', hashFile('polyfills.js'));
$('body script[src="build/vendor.js"]').attr('src', hashFile('vendor.js'));

fs.writeFileSync(indexPath, $.html());
于 2018-06-01T19:29:10.793 回答