webpack-contrib / closure-webpack-plugin

Webpack Google Closure Compiler and Closure Library plugin -
https://developers.google.com/closure/
MIT License
434 stars 60 forks source link

Goog.Module: multiple entry points don't work properly #52

Open samilyak opened 6 years ago

samilyak commented 6 years ago

Hi guys,

I have an issue with a multi-module build (I'm talking about output modules here related to code splitting and --module flag).

Here's my webpack config:

var path = require('path');
var webpack = require('webpack');
var ClosurePlugin = require("closure-webpack-plugin");

module.exports = {

  context: path.resolve(__dirname),

  entry: {
    'module1': './module1.js',
    'module2': './module2.js',
  },

  output: {
    filename: '[name].build.js',
    path: path.resolve(__dirname, '../www/js')
  },

  plugins: [
    new ClosurePlugin({
      mode: 'AGGRESSIVE_BUNDLE',
      closureLibraryBase: require.resolve('google-closure-library/closure/goog/base'),
      deps: [
        path.resolve(__dirname, './my-deps.js')
      ]
    }, {
      compilation_level: 'WHITESPACE_ONLY',
    }),

    new webpack.optimize.CommonsChunkPlugin({
      name: 'module1',
      minChunks: 2
    })
  ]

};

module1.js:

goog.provide('module1');

console.info('module1');

module2.js:

goog.provide('module2');
goog.require('module1');

console.info('module2');

Here's the output generated into module2.build.js:

webpackJsonp([0], function(__wpcc){'use strict';goog.provide("module2");goog.require("module1");console.info("module2");});

The problem is the inner function is never called.

When both module1.build.js and module2.build.js are linked to the page, I only see module1 string in the console output. There's no module2 string.

This behaviour is reproducible for any number of modules more than 1 and any compilation_level.


On the other hand, if I comment out ClosurePlugin in the config, both modules run when loaded. Here's module2.build.js in this case:

webpackJsonp([1],[
/* 0 */,
/* 1 */
/***/ (function(module, exports) {

goog.provide('module2');
goog.require('module1');

console.info('module2');

/***/ })
],[1]);

For this one I see both module1 and module2 strings in the console output.

This example covers only a case with 1 output module.


I feel like it's not an intended behaviour, that's why opened this issue.

I have node v8.9.4 on OSX 10.12.6.

ChadKillingsworth commented 6 years ago

The difficulty here is you are attempting to both code split and directly reference. This probably should have been an error.

samilyak commented 6 years ago

@ChadKillingsworth well, I think this is more or less a common use-case. Main module (module1) contains some low-level functionality, while other modules loaded separately (module2) might use that functionality.

ChadKillingsworth commented 6 years ago

This is a deficiency in the goog.module pattern. To my knowledge, there isn't a dynamic loading function similar to import() or require.ensure(). I think you would need to use one of those methods to load the child chunk.

The clue here was that you used an entry point to force splitting code. You should not really do that. It will duplicate the polyfills and cause the issues you noticed.

You can use goog.module.get in the child chunk to reference a goog.module that was loaded with the parent chunk.

samilyak commented 6 years ago

In my use-case I don't load resulting modules dynamically via js.

I load them via static <script> tag on different pages, i.e. <script src="module1.build.js"> is always in html on any page while <script src="module2.build.js"> is in html only on a specific page which module2 is responsible for (this is just a simplest example of course).

The clue here was that you used an entry point to force splitting code. You should not really do that.

Why? I had an impression that entry array in config is treated by this plugin as closure modules.

Otherwise I see no other way to tell the plugin how I'd like to split my result build. Is this correct?

My current build process is based on plovr and its modules config key. It does exactly what I described and takes care about code reshuffling across resulting modules (leveraging the dependancy tree between modules). I was just considering closure-webpack-plugin as a replacement for it.

Do you think it's achievable in a current version of the plugin, or there's no way to make it work the way I want at the moment?

ChadKillingsworth commented 6 years ago

It's possible to handle this. However I'm also going to be investigating the possibility of a loader that converts goog modules into standard CommonJS.

Dan1ve commented 5 years ago

Any news about this?

I think I have the same problem. My config is quite similar to the example referenced above, except for the entry section:

entry: {
    viewA: './src/ViewA/Index.js',
    viewB: './src/ViewB/Index.js'
 },

I use goog.modules instead of goog.provide consistently in my code. Interestingly,

Any hints where this might go wrong in the plugin's code?

ChadKillingsworth commented 4 years ago

The issue is that there exists a set of goog.modules that are shared between 2 different entry points. Those need split off into a common chunk. To do that with webpack, you need to have a dynamic loading statement that allows webpack to split the common files out into a unique chunk.

With ES Modules you would do something like:

./module1.js

import('./common.js').then(commonExports => {
  console.log(commonExports.default);
});

Webpack would automatically code split at this point and create separate JS modules. For goog modules, the plugin would have to be updated to recognize this type of late loading behavior as well.

You could try something like:

./module1.js

goog.forwardDeclare('common');
import('./common.js').then(commonExports => {
  console.log(common.foo);
});

I'm not sure that will work, but that's the best option I have.

DreierF commented 4 years ago

@ChadKillingsworth Thanks for your response!

This unfortunately does not seem to work ATM. I created a minimal example project here that reproduces the crash: https://github.com/DreierF/closure-webpack-plugin/pull/1

The problem seems to be that the dynamically loaded chunks do not point to the entry point chunk as their parent and therefore the source files of the dynamic chunk are not passed to the closure compiler. As they are also no entry points themselves they never participate in any compilation. Was just an issue with a destructuring assignment. Please have a look at my PR