webpackionic2ionic-cli

Ionic 2 change name of main.js ( webpack.js output.filename setting)


We have an Ionic 2 App that is deployed natively and also to the web. When building I use npm run build --prod --release. That just wraps ionic build.

I'm trying to update our build process to be able to swap out the default main.js. that is in the index.html

So I want to be able to change this file from:

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

with (autogenerated hash)

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

Step 1 is to generate the file. I was able to successfully generate the right file each build by using the webpack output.filename setting.

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

When I build I can see it's correctly generating the source file but shortly after completing the ionic build fails with a message saying it can't find build/main.js. That was the original file name so I think I need to somehow let ionic know I'm changing the name of the main.js file.

Error:

[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'

I'm not sure how to update ionic build so that it knows to look for the dynamically generated main.js filename.


Solution

  • EDIT

    Much simpler solution that is much less likely to break when ionic has manjor updates.

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


    So cache busting with ionic. It's a hackish solution but it works for now. The problem is ionic build system abstracts a little too much at times. Back in October, it was asked if there was a way to implement cache busting. The ionic team responded that they might consider it in the future, but there hasn't been any activity on it since. Here's the github issue.

    So I'll show all the changes to webpack config and package.json and then explain everything.

    The config section of your package.json should look like this.

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

    For your webpack config, your entry and output can remain the same. Make sure you have required the following modules and then you'll want to add the following plugins:

    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
    ]
    

    where updateFileName is as follows

    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)
      );
    }
    

    So what's actually happening here? Fist off, in the package.json, we are going to have to generate a new uglify config for the ionic build process. You can change the file name in the middle of the build and as long as you assign the new name to process.env.IONIC_OUTPUT_JS_FILE_NAME then the rest of the build will work fine, except the uglify step will still look for the default name, main.js. We'll see how we generate that below.

    Now for the three pluggins we're adding.

    The first one does some magic. It's really configurable. How it's set up, it takes a default index.html, sets the title, injects a <script> tag for the javascript output, and then write's it to where you've specified in the filename property. If you're using the default index.html that comes from an ionic starter app, then all you need to do is get rid of the <script src="build/main.js"></script> and it'll automagically put the new link in their for the filename with the hash in it. Docs here.

    The next plugin outputs a manifest file for us so that we can know what the filename is with the hash. By default, it outputs it in www/build/. Docs here.

    The next plugin is what assigns the new file name to process.env.IONIC_OUTPUT_JS_FILE_NAME and generates the new uglify config for us. Pretty much we grab the manifest that was output, write a new uglify config to the www directory, and then assign the new file name from what we got out of the manifest.

    So pretty much that's it. If you don't want the cache busting for dev, keep the html pluggin, get rid of the other two, and then change the output filename back to process.env.IONIC_OUTPUT_JS_FILE_NAME. If you do this, you wont need to reference the main js file in your src/index.html at all. It'll get put in whether your running dev or prod. For more info on using different webpack settups for different environments, check this out.

    UPDATE:

    For ionic 3:

    1. Make sure you have these settings in the compilerOptions of your tsconfig:

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

    1. npm i cheerio --save-dev
    2. in your webpack config add var cheerio = require('cheerio')
    3. Get rid of the Webpack Manifest plugin.
    4. Change updateFileName to the following:

      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. Get rid of the Html Webpack Plugin

    6. In place of the html plugin, put the following function in the plugins array in your webpack config:

      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()
      );
        });
      }