Reputation: 1007
My team is switching to webpack and I've run into a problem I need help with.
I'm not sure why, but webpack does not appear to expose dependencies outside of each file. Unfortunately, this is breaking a pattern we use for abstracting table render logic.
foo.renderers.DefaultCellRenderer
class DefaultCellRenderer
renderCell: (data) ->
... render logic goes here ...
foo.renderers.ComplexCellRenderer
class ComplexCellRenderer
renderCell: (data) ->
... render logic goes here ...
foo.mixins.SimpleTableMixin
require foo.mixins.BaseTableMixin
class SimpleTableMixin
constructor: (@data) ->
foo.mixins.BaseTableMixin.apply @
@columns = [
{ label: 'Col 1' }
{ label: 'Col 2' }
]
foo.mixins.ComplexTableMixin
require foo.mixins.BaseTableMixin
require foo.renderers.ComplexCellRenderer
class ComplexTableMixin
constructor: (@data) ->
foo.mixins.BaseTableMixin.apply @
@columns = [
{ label: 'Col 1' }
{ label: 'Col 2', renderer: 'ComplexCellRenderer' }
]
foo.mixins.BaseTableMixin
require foo.renderers.DefaultCellRenderer
class BaseTableMixin
render: (container) =>
# render table header...
# render table body...
for row in @data
for metadata, cellIndex in @columns
name = metadata.renderer or 'DefaultCellRenderer'
target = foo.renderers[name]
container.append target.renderCell row[cellIndex]
This is an extremely simplified example, but essentially this approach allows us to compose very complex tables with layers of behavior that are dynamically configurable at run time.
Prior to implementing webpack, this approach worked because 'foo.renderers' was global and any class requiring custom rendering was responsible for declaring that dependency, guaranteeing its existence when called by the base .render() function...
However, since moving to webpack, these renderers are no longer available unless I explicitly require then within the base table mix-in. This breaks the fundamental benefit of this approach, which is to make our tables extensible without having to modify the underlying logic.
Additionally, I cannot pass the renderer reference directly in the columns array, as app state needs to be serializable to JSON so it can be dehydrated / rehydrated at will.
Taking all of this into account, can anyone tell me if there is a way to ensure these dependencies are available within the base table mix-in without having to explicitly declare them in that file (thus breaking the Open/Close principle)?
Thanks!
UPDATE So based on dtothefp's answer below, I ended up creating a wrapper class that dynamically required all the renderers in the same or child directories... so I just have to require one reference instead of individual ones (although, they can each be required individually as well, to avoid circular references)
/foo/common/renderers/Renderers.coffee
goog.provide 'foo.common.renderers'
context = require.context './', true, /^.+\.js$/
context.keys().forEach (key) ->
renderers = context(key)?.foo?.common?.renderers
_.extend foo.common.renderers, renderers if renderers?
return
Upvotes: 0
Views: 212
Reputation: 1364
Try using the expose-loader
https://github.com/webpack-contrib/expose-loader
module: {
rules: [{
test: path.join(yourSrcRoot, 'foo', 'renderers', 'DefaultCellRenderer'),
use: [{
loader: 'expose-loader',
options: 'foo.renderers.DefaultCellRenderer' // not sure if this works???
}]
}]
}
Seems like trying to move from whatever you had before to Webpack using globals on a namespace is an anti-pattern, but this might get you started.
or better yet why don't you just bootstrap this whole thing at the start of your bundle?
// index.js
const foos = require.context('./foo', true, /(renderers|mixins)\/.+\.js$/);
global.foo = {};
global.foo.mixins = {};
global.foo.renderers = {};
foos.keys().forEach((fp) => {
const [dir] = fp.split('/');
global.foo[dir] = foos(fp);
});
Not sure what you mean by "This breaks the fundamental benefit of this approach, which is to make our tables extensible without having to modify the underlying logic." If you want to use a module loader but don't want do import dependencies seems like you've missed the point.
Upvotes: 1