-
Notifications
You must be signed in to change notification settings - Fork 48
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
require.context is not a function #48
Comments
Yes, there's no |
@halt-hammerzeit the code line above fetches all This file is bundled with
EDIT: Hm, well, the tool has to "scrape" the javascript files to figure out what to extract.. |
@halt-hammerzeit could it be that your tool is figuring out what is to be required and tries to follow that require to find more files to be "isomorphed", but the tool is not capable of following |
I asked you not about what your code does but about what Seems that it is used for dynamic
This |
The tool instruments |
@halt-hammerzeit err, how would I do that? The file has to be called, ref:
Hm, could I wrap this in a try catch? |
Can't tell until I see the code |
Sorry, I am a bit frustrated with getting server-side rendering to work here and this is blocker number "n+1", so bare with me on this 😫 This is the content of
And the files is imported from another file which provides memoized selectors for the entire application:
The clue here is that the application will never know the number of content folders, it will only know that there exists |
Well, for example, you could do something like this: //this file includes all contents indexed by directory name
let contents
if (__CLIENT__)
{
contents = require.context('./', true, /content\.yml$/);
}
else
{
const path = require('path')
const fs = require('fs')
for (let child of fs.readdirSync(__dirname))
{
console.log(child)
if (fs.statSync(path.join(__dirname, child)).isDirectory())
{
contents[path] = require(path.join(__dirname, child, 'content.yml'))
}
}
}
|
In pseudo-code, I think this could be solved like this:
|
Or you can implement I have added this feature to the To do list for whoever wants to implement this in the future:
|
@halt-hammerzeit ok, I got the code running, but stuck with a new problem, how can I implement the loader logic on these files? The yml files is now required raw without the loader:
The code:
I've tried to include the loader directly:
But that results in error because of missing the |
So you are telling that |
@phun-ky yes this is a problem stemming from developing your code in webpack which completely redefines it's version of require and then running the code in Node to do isomorphic. webpack-isomorphic-tools attempts to solve this but can't do everything and once you solve this problem you may run into others where your client code uses references or libs that reference browser stuff like document, etc. my solution is to still use isomorphic tools for stats generation because it is awesome for that and then compile all my isomorphic entries with webpack target: node. Then I can easily create a loader that reads any of my asset requires from stats json and another that mocks any browser syntax that is breaking in node. Then you can just require your webpack bundles (assuming you compile with output commonjs) and call reacts render to string on them. I believe @halt-hammerzeit has an example repo where he does something similar but much more eloquently https://github.com/halt-hammerzeit/webpack-react-redux-isomorphic-render-example |
For anyone who got here from Google or from the To do list: While looking for a possibility to implement The If there was a way to get a hold of that The function itself would look like this: require.context = function(base, scan_subdirectories, regular_expression) {
const contents = {}
function read_directory(directory) {
for (let child of fs.readdirSync(directory)) {
const full_path = path.join(directory, child)
if (fs.statSync(full_path).isDirectory()) {
if (scan_subdirectories) {
read_directory(full_path)
}
} else {
if (regular_expression && !regular_expression.match(full_path)) {
continue
}
contents[path.relative(base, full_path)] = require(full_path)
}
}
read_directory(base)
return contents
} The dirty way I found is: const original_compile = require('module').prototype._compile
require('module').prototype._compile = function(content, filename)
{
var preamble = ''
if (starts_with(content, `'use strict'`) || starts_with(content, `"use strict"`))
{
preamble = `"use strict";`
}
preamble += `require.context = function() { console.log('require.context code here') };;;`
content = preamble + content
// return value is undefined
return original_compile.call(this, content, filename)
} It's "dirty" because it prepends some code to the |
@dtothefp Oh, hi there. Thanks for participation. |
@dtothefp Could you please post an example how you are accomplishing this? I am trying to do the exact same thing but I can't seem to get my image assets to line up.
|
So much trouble, and all I want is to let the server (index.html) know about the store (react-redux) so it can update the meta-tags based on which page you are on :/ #fml Will see if I can get something to work :S |
@chrisblossom you are essentially doing something flux/state wise on the server different from client, ie if you are toggling a class based on query params/cookie/async-something before I ended up making a queue for client actions and then don't emit these actions until |
@chrisblossom @phun-ky below are some examples of the way I do it using a static template generator with Nunjucks //stats loader
import path from 'path';
import fs from 'fs-extra';
import renameKey from '../../../utils/rename-key';
/**
* Webpack loader used for "SERVER" build to load local
* assets such as Images and CSS from the stats created
* by Webpack Isomporphic Tools
* @param {Buffer} content
* @return {undefined} uses the `cb` callback to pass data to the `compiler`
*/
export default function(content) {
this.cacheable && this.cacheable();
const cb = this.async();
const resourcePath = renameKey(this.resourcePath);
fs.readJson(path.join(process.cwd(), 'dist', 'webpack-main-stats.json'), (err, data) => {
if (err) {
cb(err);
} else {
const {assets} = data;
const [assetData] = Object.keys(assets).reduce((list, assetPath) => {
const asset = assets[assetPath];
return resourcePath === renameKey(assetPath) ? [...list, asset] : list;
}, []);
cb(null, `module.exports = ${JSON.stringify(assetData)};`);
}
});
}
//mocks loader
/**
* Webpack loader used for "SERVER" build to load local
* assets such as Images and CSS from the stats created
* by Webpack Isomporphic Tools
* @param {Buffer} content
* @return {undefined} uses the `cb` callback to pass data to the `compiler`
*/
export default function(content) {
const mocks = [
{
re: /(?:global|window)?\.?ga\(/,
sub: 'global.ga = () => {}'
},
{
re: /(?:global|window)?\.?Raven\.captureException\(/,
sub: 'global.Raven = {captureException: () => {}}'
},
{
re: /(?:global|window)?\.?optimizely\./,
sub: 'global.optimizely = {push: () => {}}'
},
{
re: /navigator\..+$/,
sub: `navigator = {
userAgent: '',
geolocation: {
getCurrentPosition() {}
}
}`
},
{
re: /(?:global|window)?\.?silverpop\./,
sub: 'global.silverpop = {trackEvent: () => {}, flush: () => {}}'
},
{
re: /document\.?(?:cookie|documentElement)?/,
sub: `global.document = {
get cookie() {
return '';
},
set cookie(val) {},
cookie: {
split() {}
},
documentElement: {
style: {}
}
}`
}
];
const mockSubs = mocks.reduce((str, data) => {
const {re, sub} = data;
if (re.test(content)) {
str = `\n${sub}\n` + str;
}
return str;
}, '');
return mockSubs + content;
}
//loaders.js
{
test: toolsPlugin.regular_expression('images'),
loader: SERVER ? join(__dirname, 'iso-tools-stats-loader') : fileLoader
},
cssLoader = [
join(__dirname, 'iso-tools-stats-loader'),
'postcss-loader'
].join('!'); Obviously with this approach you need to build your complete bundle first so you have all the stats from //static template generation loader
import glob from 'globby';
import webpack from 'webpack';
import MemoryFS from 'memory-fs';
import gutil from 'gulp-util';
import renameKey from '../../utils/rename-key';
import makeConfig from '../../config';
import makeWebpackConfig from '../webpack/make-webpack-config';
import compile from '../../utils/compile-module';
const {colors, log} = gutil;
const {magenta, blue} = colors;
/**
* Loader to compile React components and es6 functions with Webpack
* memory-fs and put them on the `snippets` Assemble collection
*
* @param {Object} collection instance provided by assemble
*
* @return {Function} function re-defining a `load` method
*/
export default function(collection) {
/**
* @param {String|Array} patterns glob patterns
* @param {Object} options glob options
* @param {Object} data any additional context
* @param {Function} cb callback to be called at the end
*
* @return {undefined} use the cb
*/
collection.load = (patterns, options = {}, data = {}, cb) => {
const files = glob.sync(patterns, options);
const entry = files.reduce((acc, fp) => {
const name = renameKey(fp);
log(`Compiling ${magenta(name)} component for isomorphic build`);
return {
...acc,
[name]: [fp]
};
}, {});
const config = makeConfig({
ENV: 'server',
entry
});
const fs = new MemoryFS();
const webpackConfig = makeWebpackConfig(config);
webpackConfig.plugins.push(
function() {
this.plugin('done', (stats) => {
const {errors} = stats.compilation || {};
if (errors && errors.length) {
log(errors);
}
});
}
);
const compiler = webpack(webpackConfig);
const {sources, utils} = config;
const {buildDir, scriptDir} = sources;
const {addbase} = utils;
compiler.outputFileSystem = fs;
compiler.run((err, stats) => {
if (err) return cb(err);
Object.keys(entry).forEach(file => {
const filename = `${file}.js`;
const fp = addbase(buildDir, scriptDir, filename);
log(`Finished Compiling ${blue(file)} component`);
const contents = fs.readFileSync(fp);
collection.addView(file, {
path: file,
contents,
fn: compile(contents)
});
});
cb(null);
});
};
}
//compile
import _ from 'lodash';
/**
* Utility to extract a string of JS into JavaScript "runnable" code
* @param {String} content pre-combiled string of JS code
* @param {String|undefined} key property to pull off of `module.exports`
*
* @return {Funtion|Object} string converted to "runnable" JS
*/
export default function(content, key) {
const compileTarget = _.isFunction(content.toString) ? content.toString() : content;
const m = new module.constructor();
m.paths = module.paths;
m._compile(compileTarget);
return key ? m.exports[key] : m.exports;
} |
@dtothefp : will try that tomorrow and see if I get any further. |
@dtothefp Thanks for the response. Could you also post your webpack client and server build configuration? I think I may have misunderstood what you are doing. I fixed my issue by using the same output.publicPath for my node webpack config as my client. I am not 100% sure if the server is fully rendering the page though. |
You seem to not prepending webpack configuration's |
You can do that using If you feel a need for a specific example with |
@halt-hammerzeit thank you, that was exactly my issue. Because I am using webpack for both my frontend and backend, I am running them both though the same loaders. I think this solves most of what webpack-isomorphic-tools does with the exception of asset tracking. Currently I am using webpack-isomorphic-tools to export webpack-assets.json to load assets in html.js. Is there a benefit I am missing out on from webpack-isomorphic-tools by doing this? |
@chrisblossom I guess you don't miss anything. It's just a question of why write your own asset loading mechanism if it's already written. |
@halt-hammerzeit Using webpack-isomorphic-tools for that. Still smoothing out the edges on this, but basically: dev.client.webpack.config.js: ...
import WebpackIsomorphicToolsPlugin from 'webpack-isomorphic-tools/plugin';
import webpackIsomorphicToolsPluginConfig from './webpack_isomorphic_tools';
import NodeExternals from 'webpack-node-externals';
const nodeModules = new NodeExternals();
const sharedConfig = {
...
module: {
loaders: [{
test: /\.scss$/,
loader: ExtractTextPlugin.extract('style',
'css?modules&importLoaders=2&localIdentName=[local]___[hash:base64:5]!postcss!sass?outputStyle=expanded&sourceMap=true' +
'&sourceMapContents=true'),
}, {
test: /\.png$/,
loader: 'url-loader?limit=1',
}],
},
...
}
const clientConfig = {
...
output: {
path: buildPath,
filename: '[name].js',
chunkFilename: '[name]-[chunkhash].js',
publicPath: `http://${host}:${webpackHmrPort}/dist/`,
},
target: 'web',
plugins: [
...
new WebpackIsomorphicToolsPlugin(webpackIsomorphicToolsPluginConfig),
...
],
...
}
// Possibly move global universalAssets to server.js. At very least rename.
const serverConfig = {
...
output: {
path: buildPath,
filename: outputFile,
libraryTarget: 'commonjs2',
publicPath: `http://${host}:${webpackHmrPort}/dist/`,
},
target: 'node',
externals: [nodeModules],
plugins: [
...
new webpack.DefinePlugin(
universalAssets: JSON.stringify(require(path.resolve(projectRootPath, 'webpack-assets.json')))
),
...
],
...
}
// Merge configs server.js: ...
res.send(`<!doctype html>\n${ReactDOM.renderToString(
<Html assets={universalAssets} component={component} store={store}/>)}`)
... The biggest downside to this I've found so far is it writes assets twice. I am sure I can find a workaround for that though. I am correct in thinking requiring webpack-assets.json directly opposed something like this below will yield the same results: ...
const webpackIsomorphicTools = new WebpackIsomorphicTools(WebpackIsomorphicToolsConfig);
webpackIsomorphicTools
.development()
const serverConfig = {
...
plugins: [
...
new webpack.DefinePlugin(
universalAssets: JSON.stringify(webpackIsomorphicTools.assets())
),
...
],
... Btw, the reason I am using webpack for the backend is because babel does not recommend using babel-register in production. |
@chrisblossom hmm, interesting solution. you're defining a global variable holding the assets, i see...
Oh, didn't know about that.
Didn't understand that line but don't mind |
See: http://babeljs.io/docs/setup/#babel_register
I plan to move the global variable to a local defined var inside of server.js Webpack is running two separate configs so each file is ran through the loader twice. This means image_.png and style_.css will be written to the build directory of the node config and the client config. https://github.com/webpack/webpack/tree/master/examples/multi-compiler modules.export = [
{
// Client config
},
{
// Node config
}
]; |
@chrisblossom oh, didn't know that it writes the assets twice. By the way, I implemented support for Update: |
@halt-hammerzeit an update regarding the loading of *.yml files:
The error:
My config:
And the assets file: https://gist.github.com/phun-ky/8c808054a53cd57333f3 ( I am not obfuscating directories/variables now, takes too long 😛 ) The stats file: https://gist.github.com/phun-ky/96f0277c59c6cddd5f5b And this is the loader I want to use: https://gist.github.com/phun-ky/e8ed6778c367bcdbd36d |
@phun-ky Well, why do you think that Also, after you get this thing working, try out the new version which supports |
@halt-hammerzeit The code above is with the latest version, forgot to mention that. The require.context returns an array (due to the loader) without isomorphic tools. The issue is that the loader is not kicking in. |
@chrisblossom regarding: #48 (comment) , I am trying this approach. But would you have all loaders in the shared config? When should I use the extract plugin, and when should I use the isomorphic plugin? |
@phun-ky oh, a sophisticated loader you have. never imagined people would do that. ok, that's an interesting approach. regarding your error message, I get it now: Try |
@dtothefp regarding #48 (comment), what is renameKey? I'm trying to bind it all together:
And what should be different with these in development/production? |
@halt-hammerzeit regarding
I would'nt call that amount of code for a boiler plate :S Tried to take a look at it now and it's a bit confusing to say at least. This whole thing becomes off topic, but I am more close the the "going postal" point than solving this. Going away for a week, need to clear my mind. Will abandon this thread if nothing more comes. Thanks for the patience. |
@phun-ky ok, at least the initial issue with There's nothing difficult in the world once you learn how to formulate the right questions. |
FYI |
I'm getting this error when running
webpack-isomorphic-tools' with
webpack`How to I circumvent/fix this? the code:
is used to fetch the data files to an array on runtime. The directory tree looks like this:
The text was updated successfully, but these errors were encountered: