Django's 'static' tag generates urls using STATIC_URL, which results in something like '/static/myapp/js/bundle.js'
Mean while, webpack-dev-server is serving bundles from the url 'localhost:3000'
My question is how do I get Django 'static' template tag to generate a different url ( which points to webpack dev server) for js bundles. Of course I can hardcode it in the template, but that would not be a good solution.
Below is my project configuration
webpack.config.js
const path = require('path')
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const BundleTracker = require('webpack-bundle-tracker')
module.exports = {
mode: 'development',
context: path.dirname(path.resolve(__dirname)),
entry: {
index: './typescript_src/index.ts',
},
output: {
path: path.resolve('./myproject/assets/myapp/bundles/'),
filename: "[name]-[hash].js"
},
resolve: {
extensions: ['.ts', '.js' ]
},
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{
test: /\.ts$/,
use: 'ts-loader',
exclude: /node_modules/
}
]
},
plugins: [
new CleanWebpackPlugin(),
new BundleTracker({filename: './myproject/webpack-stats.json'})
],
devServer: {
port: 3000,
publicPath: '/myapp/bundles/',
// hot: true,
headers: {
"Access-Control-Allow-Origin": "http://127.0.0.1:8000", /**Django dev server */
}
}
}
settings.py
WEBPACK_LOADER = {
'DEFAULT': {
'CACHE': not DEBUG,
'BUNDLE_DIR_NAME': 'myapp/bundles/', # must end with slash
'STATS_FILE': os.path.join(BASE_DIR, 'webpack-stats.json'),
'POLL_INTERVAL': 0.1,
'TIMEOUT': None,
'IGNORE': [r'.+\.hot-update.js', r'.+\.map']
}
}
STATIC_URL = '/static/'
STATICFILES_DIRS = (
os.path.join(BASE_DIR, 'assets'),
)
Initially I decided webpack should serve other static files as well during development
webpack.config.js
devServer: {
port: 3000,
publicPath: '/myapp/bundles/',
contentBase: path.resolve('./myproject/assets')
// hot: true,
headers: {
"Access-Control-Allow-Origin": "http://127.0.0.1:8000", /**Django dev server */
}
settings.py
# in development mode serve from wepack dev server
if DEBUG:
STATIC_URL = 'http://localhost:3000/'
else:
STATIC_URL = '/static/'
But I later realized I have to serve static files of other apps (admin, tinymce, ...), which is impossible for webpack Dev server to reach
The problem here is that the url generated by 'render_bundle' tag of django-webpack-loader (/static/myapp/bundles/bundle-name.js) will result in a Http 404 because webpack-dev-server keeps the generated bundle in memory and not on disk
Also if I set
STATIC_URL = localhost:3000
and configure webpack-dev-server to serve other static files of my app, static files of other apps won't be served
Let's analyze the issue:
We have 2 servers and we want to route requests to one or the other based on the path requested:
"/static/webpackbundles/** ==> webpack dev server
other paths ==> django dev server
This is exactly the job of a proxy server, it can be achieved with a third server (haproxy, nginx ...), but that might seem like an overkill, especially if we know that webpack dev server can be used as a proxy! (https://webpack.js.org/configuration/dev-server/#devserverproxy)
webpack.config.js
const path = require('path');
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: 'main.js',
path: '/path/to/django_project/django_project/static/webpackbundles',
publicPath: '/static/webpackbundles/',
},
devServer: {
contentBase: '/path/to/django_project/django_project/static/webpackbundles',
hot: true,
proxy: {
'!/static/webpackbundles/**': {
target: 'http://localhost:8000', // points to django dev server
changeOrigin: true,
},
},
},
};
In your django template:
<script type="text/javascript" src="{% static 'webpackbundles/main.js' %}"></script>
Now access your django app/site using webpack dev server address:
ex: http://localhost:8081
With this simple config you'll have browser auto refresh and hot module replacement.
You will not need to change anything in django, also no need for django-webpack-loader
Building on #Ejez answer, i was able to configure webpack-dev-server to serve all static files (including, media files)
webpack.config.js
module.exports = {
// project root (usually package.json dir)
context: path.dirname(path.resolve(__dirname)),
output: {
path: path.resolve('./path/to/bundles/'),
filename: "[name]-[hash].js"
},
resolve: {
extensions: ['.ts', '.js' ]
},
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{
test: /\.ts$/,
use: 'ts-loader',
exclude: /node_modules/
}
]
},
plugins: [
new CleanWebpackPlugin(),
new BundleTracker({filename: '/path/to/webpack-stats.json'})
],
optimization: {
splitChunks: {
chunks: 'all',
name: 'lib' // bundle all their party libraries in lib.js
}
},
devServer: {
// if you do not mind webpack serving static files of other apps
// collect them (with django collectstatic) into /static_root/static/
// this way webpack-dev-server can serve from your own app's /static/
// directory and also /static_root/static/ directory (which contains
// static files of other apps
contentBase: [path.resolve('./project'), path.resolve('./project/static_root')],
// webpack bundles will be served from http://locahost:3000/static/project/bundles/
publicPath: '/static/project/bundles/',
port: 3000,
// proxy all request except (static and media files) to django dev server
proxy: [{
context: ['**', '!/static/**', '!/media/**'],
target: 'http://localhost:8000',
changeOrigin: true,
}]
}
}
Now you can access your project from webpack-dev-server url localhost:3000. Do not forget to start both dev servers (webpack and django)
Is generally not justified using STATICFILES_DIRS,
instead use STATIC_ROOT.
If You have permission to copy static files to the project folder then use STATIC_ROOT.
Related
Recently I came across the same issue as the post "historyApiFallback doesn't work in Webpack dev server".
I will first quote the accepted answer in that post.
Answer:
I meet the same question today. let config in webpack.config.js:
output.publicPath be equal to devServer.historyApiFallback.index and
point out html file route.my webpack-dev-server version is 1.10.1 and work well. http://webpack.github.io/docs/webpack-dev-server.html#the-historyapifallback-option doesn't work, you must point out html file route.
module.exports = {
entry: "./src/app/index.js",
output: {
path: path.resolve(__dirname, 'build'),
publicPath: 'build',
filename: 'bundle-main.js'
},
devServer: {
historyApiFallback:{
index:'build/index.html'
},
},
};
I tried to use this answer to fix the problem(set output.publicPath: 'dist' and devServer.historyApiFallback:{index:'dist/index.html'})
but somehow it didn't work.
After some search I found this page. According to the description in the page:
This section is for everyone who ran into this problem in development
using webpack-dev-server.. Just as above, what we need to do it tell
Webpack Dev Sever to redirect all server requests to /index.html.
There are just two properties in your webpack config you need to set
to do this, publicPath and historyApiFallback.
module.exports = {
entry: './app/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'index_bundle.js',
publicPath: '/'
},
module: {
rules: [
{ test: /\.(js)$/, use: 'babel-loader' },
{ test: /\.css$/, use: [ 'style-loader', 'css-loader' ]}
]
},
devServer: {
historyApiFallback: true,
},
plugins: [
new HtmlWebpackPlugin({
template: 'app/index.html'
})
]
};
According to the config I modified my devServer.historyApiFallback to be true, and output.publicPath to be /.
My webpack config:
const webpack = require("webpack")
const path = require("path")
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: "development",
entry: {
app: "./src/base/index.js"
},
output: {
filename: "[name].bundle.js",
publicPath: '/',
path: path.resolve(__dirname, "dist")
},
devtool: 'inline-source-map',
devServer: {
hot: true,
port: 3000,
historyApiFallback: true
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
},
{
test: /\.(scss|css)$/,
use: [
"style-loader",
"css-loader",
"sass-loader"
]
},
{
test: /\.(pdf|jpg|png|gif|svg|ico)$/,
use: [
{
loader: 'url-loader'
},
]
},
]
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: './src/base/index.html'
}),
new webpack.HotModuleReplacementPlugin()
],
}
Everything seemed working now.
But I have the puzzle that I don't know why it's working.
To be specific
devServer.historyApiFallback: true is clear according to webpack doc, so I'm not doubt about that part.
output.publicPath:/ is pretty vague for me though.
Question:
If I tried to use something like output.publicPath:/public, it
will not work. So why I must use output.publicPath:/ here?
How output.publicPath:/ can tell webpack-devserver to find the
right place and server the right index.html(which is generated by the
devserver I believe)?
Sry if it's a bit tedious. I just want to provide some detail.
But I have the puzzle that I don't know why it's working.
Setting 1
In webpack.config.js the setting
output: {
...
publicPath: '/static1/',
},
tells webpack to embed '/static1/' into the bundle's path in the generated .html file:
<script src="/static1/<name>.bundle.js" type="text/javascript"></script>
You can open the generated .html file(s) on disk and see the above tag with '/static1/' prepended to the bundle.
Setting 2
This setting:
devServer: {
publicPath: '/static/', // different from 'static1' we used above
tells webpack-dev-server to create a route handler for the path /static and serve resourses e.g. /static/<name>.bundle.js. The webpack-dev-server is based on Express which uses route handlers e.g. app.use(/mypath, ...); to serve requests.
If now you point a browser to localhost:8080, you will see blank screen. Righ-click on it to see Page Source. You will see the above <script>tag that makes the browser issue GET request for the bundle using the path /static1/xxx that doesn't work because you didn't tell webpack-dev-server to create a route handler for this path. Now type in the browser navigation bar
http://localhost:8080/static/<name>.bundle.js and you will see the internals of your bundle.
Eliminate the discrepancy between static1 and static and the page will render. In your case it works because one setting is set explicitly to '/' and the second one defaults to the same value.
Setting 3
historyApiFallback has a more narrow scope than other two settings because it is used with SPAs only. During the initial rendering a user sees the landing page of the SPA e.g. /mysample.html. This is the file with our <script> tag shown above. It should be used without any path like /static prepended to it:
historyApiFallback: {
...
index: mysample.html,
because Setting1 and Setting2 apply to bundles, not to bundle-containing .html pages.
Faced the same problem, it was succeeded to solve, having specified a route, instead of a path to index.html
devServer: {
publicPath: `/myApp`,
historyApiFallback: {
rewrites: [
{ from: /\/myApp/, to: `/myApp` }
]
}
}
I am building a Web Application using MEAN stack.
I am using webpack to bundle my files.
In my project, I have two folders called 1.public/assets (in this assets folder I have separate folders called CSS, js, etc.. which contains various js and CSS.
and I have a folder called 2.client (in this I have my AngularJs code like controllers.js, services.js)
I am using webpack to bundle my client code.
const path = require('path');
const glob = require('glob');
const CleanWebpackPlugin = require('clean-webpack-plugin');
// const CopyWebpackPlugin = require('copy-webpack-plugin');
const outputDirectory = 'dist';
module.exports = {
mode: 'development',
target: 'web',
entry: {
app: glob.sync('./client/*.js'),
},
output: {
path: path.resolve(__dirname, outputDirectory),
filename: '[name].bundle.js',
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
query: {
presets: ['env', 'stage-0'],
},
},
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
{
test: /\.(png|woff|woff2|eot|ttf|svg|jpg)$/,
loader: 'url-loader?limit=100000',
},
],
},
devServer: {
port: 3005,
open: false,
disableHostCheck: true,
proxy: {
'/': 'http://localhost:8005',
},
},
plugins: [
new CleanWebpackPlugin([outputDirectory]),
// new CopyWebpackPlugin([
// { from: 'public/assets' },
// ]),
],
};
I am just bundling my client folder and compiling that into app.bundle.js,
How to compile assets?
NOTE: I am using AngularJs v1.
Webpack starts from each entry point (you could have more than one entry point) and creates a dependency graph. Files/assets are added to the dependency graph when you have imported them into your application through the use of require and import statements.
What is not clear in your question is the relationship between your client folder and your public/assets folder, and whether they are linked. However, if none of the files in your client folder depend upon (require, import) any of the assets in your public/assets folder then they won't be in webpack's dependency graph, and therefore won't be transpiled and bundled.
(webpack.config.js file content below)
I'm trying to make a webpack exclusion on node modules.
I found that using webpack-node-externals works for it but using that on my common config causes this other error:
Require is not defined on reflect-metadata - __webpack_require__ issue
So... I was wondering how can i exclude webpack bundling also on the browser side without getting any issue.
My webpack version: 3.11.0
webpack-config.js
const path = require('path');
const webpack = require('webpack');
const merge = require('webpack-merge');
const AotPlugin = require('#ngtools/webpack').AotPlugin;
const CheckerPlugin = require('awesome-typescript-loader').CheckerPlugin;
var nodeExternals = require('webpack-node-externals');
module.exports = (env) => {
// Configuration in common to both client-side and server-side bundles
const isDevBuild = !(env && env.prod);
const sharedConfig = {
//externals: [nodeExternals()], // in order to ignore all modules in node_modules folder
stats: { modules: false },
context: __dirname,
resolve: { extensions: [ '.js', '.ts' ] },
output: {
filename: '[name].js',
publicPath: 'dist/' // Webpack dev middleware, if enabled, handles requests for this URL prefix
},
module: {
rules: [
{ test: /\.ts$/, use: isDevBuild ? ['awesome-typescript-loader?silent=true', 'angular2-template-loader', 'angular2-router-loader'] : '#ngtools/webpack' },
{ test: /\.html$/, use: 'html-loader?minimize=false' },
{ test: /\.css$/, use: [ 'to-string-loader', 'style-loader', isDevBuild ? 'css-loader' : 'css-loader?minimize' ] },
{ test: /\.(png|jpg|jpeg|gif|svg)$/, use: 'url-loader?limit=25000' }
]
},
plugins: [new CheckerPlugin()]
};
// Configuration for client-side bundle suitable for running in browsers
const clientBundleOutputDir = './wwwroot/dist';
const clientBundleConfig = merge(sharedConfig, {
entry: { 'main-client': './ClientApp/boot.browser.ts' },
output: { path: path.join(__dirname, clientBundleOutputDir) },
plugins: [
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('./wwwroot/dist/vendor-manifest.json')
})
].concat(isDevBuild ? [
// Plugins that apply in development builds only
new webpack.SourceMapDevToolPlugin({
filename: '[file].map', // Remove this line if you prefer inline source maps
moduleFilenameTemplate: path.relative(clientBundleOutputDir, '[resourcePath]') // Point sourcemap entries to the original file locations on disk
})
] : [
// Plugins that apply in production builds only
new webpack.optimize.UglifyJsPlugin(),
new AotPlugin({
tsConfigPath: './tsconfig.json',
entryModule: path.join(__dirname, 'ClientApp/app/app.browser.module#AppModule'),
exclude: ['./**/*.server.ts']
})
])
});
// Configuration for server-side (prerendering) bundle suitable for running in Node
const serverBundleConfig = merge(sharedConfig, {
resolve: { mainFields: ['main'] },
entry: { 'main-server': './ClientApp/boot.server.ts' },
plugins: [
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('./ClientApp/dist/vendor-manifest.json'),
sourceType: 'commonjs2',
name: './vendor'
})
].concat(isDevBuild ? [] : [
// Plugins that apply in production builds only
new AotPlugin({
tsConfigPath: './tsconfig.json',
entryModule: path.join(__dirname, 'ClientApp/app/app.server.module#AppModule'),
exclude: ['./**/*.browser.ts']
})
]),
output: {
libraryTarget: 'commonjs',
path: path.join(__dirname, './ClientApp/dist')
},
target: 'node',
externals: [nodeExternals()], // in order to ignore all modules in node_modules folder,
devtool: 'inline-source-map'
});
return [clientBundleConfig, serverBundleConfig];
};
GOT IT!
Before posting my solution, I'd like to thanks Aluan Haddad for his useful comment in my question above.
As suggested by Aluan, in fact, the problem was related to the need to use also a module loader, more than a module bundler.
So, the steps that I followed are these:
Installing requireJS ==> http://requirejs.org/docs/node.html
Removing externals: [nodeExternals()], // in order to ignore all modules in node_modules folder from my common webpack configuration and adding it under my server configuration (done before my question, but it's a really important step) [see webpack.config.js content in the question]
Adding target: 'node', before my externals point above, under my server side section (done before my question, but it's a really important step) [see webpack.config.js content in the question]
This makes sure that browser side keeps target:'web' (default target), and target becomes node just for the server.
launched webpack config vendor command manually from powershell webpack --config webpack.config.vendor.js
launched webpack config command manually from powershell webpack --config webpack.config.js
That worked for me! Hope It will works also for anyone else reading this question and encountering this issue!
I'm trying to learn Webpack configuration, and I keep getting errors in my console. It seems like my webpack app.bundle.js is not been found.
The page loads and the content of my html file displays, but not in the app.bundle.js or the html file in have in my dist directory, not untill i run mpm build.
below is the code for the webpack configuration and the error
// import node.js native path module
const path = require('path');
let webpack = require('webpack');
//require HtmlWebPackPlugin
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const port = process.env.PORT || 3000;
//define constant for paths
const paths ={
DIST: path.resolve(__dirname, 'dist'),
SRC: path.resolve(__dirname, 'src'),
JS: path.resolve(__dirname, 'src/js')
};
console.log(paths.DIST);
//webpack configuration
module.exports ={
entry: path.join(paths.JS, 'index.js'),
output: {
path: paths.DIST,
filename: 'app.bundle.js'
},
//set starting point for server
devServer: {
contentBase: paths.SRC,
host:'localhost',
port: port,
historyApiFallback: true,
open: true,
hot:true
},
//set webpack to use plugins
plugins: [
new HtmlWebpackPlugin({
template: path.join(paths.SRC, 'index.html'),
}),
new ExtractTextPlugin('style.bundle.css'),
new webpack.HotModuleReplacementPlugin()
],
//configure loaders
module: {
rules: [
//setup babel loader
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: [
'babel-loader',
],
},
//setup css loader
{
test: /\.css$/,
loader: ExtractTextPlugin.extract({
use: 'css-loader',
}),
},
{
test: /\.(png|jpg|gif)$/,
use: [
'file-loader',
],
},
],
},
//enable JS files without adding their extensions
resolve: {
extensions: ['.js', '.jsx'],
},
};
Here is the error on the browser console
Loading failed for the <script> with source “http://localhost:3000/js/app.bundle.js”
Source map error: request failed with status 404 Resource URL: http://localhost:3000/app.bundle.js Source Map URL: sockjs.js.map
The post is old but hopefully this helps some future person since I just solved a similar issue, here goes:
Ensure that the server root path and the output path makes sense for the files generating the 404 error.
Specifically, the server root path, set by "contentBase: paths.SRC" points to the SRC folder but the JS files are output to paths.DIST. When a browser attempts to access these files the URL it uses points to the wrong location. Fix by either changing content base to DIST, or adding publicPath: paths.DIST, which will override contentbase.
Link to references for contentBase, and publicPath.
The same error happened to me because i didn't run Webpack.
npx webpack
Run it on the same directory as Webpack configuration script.
I have created a project using react and flux architecture. Need to chunk the bundle.js file because by combining all the files it is creating a huge js file of 4mb which is causing problem in downloading on slow network so how to chunk the js file so that only the required libraries are included when a page opens
I am using webpack 1.x
my directory structure is
webpack.config.js file
var path = require('path');
var webpack = require('webpack');
module.exports = {
devtool: 'cheap-module-source-map',
entry: [
'./src/index'
],
output: {
path: path.join(__dirname, 'dist'),
filename: 'bundle.js',
publicPath: ''
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.optimize.CommonsChunkPlugin({
// names: ["app", "subPageA"]
// (choose the chunks, or omit for all chunks)
children: true,
// (use all children of the chunk)
async: true,
// (create an async commons chunk)
// minChunks: 3,
// (3 children must share the module before it's separated)
})
],
module: {
loaders: [{
test: /\.js$/,
loaders: ['react-hot', 'babel'],
include: path.join(__dirname, 'src')
}, {
test: /\.css$/,
exclude: /\.useable\.css$/,
loader: "style-loader!css-loader"
}, {
test: /\.useable\.css$/,
loader: "style-loader/useable!css-loader"
}, {
test: /\.png$/,
loaders: ["url-loader?mimetype=image/png"]
}, {
test: /\.(png|woff|woff2|eot|ttf|svg)$/,
loader: 'url-loader?limit=100000'
}]
}
};
server.js file
var webpack = require('webpack');
var WebpackDevServer = require('webpack-dev-server');
var config = require('./webpack.config');
new WebpackDevServer(webpack(config), {
publicPath: config.output.publicPath,
hot: true,
historyApiFallback: true
}).listen(3000, 'localhost', function(err, result) {
if (err) {
return console.log(err);
}
console.log('Listening at http://localhost:3000/');
});
index.html file
<html>
<head>
<title>Project</title>
</head>
<body>
<div id="app" />
<script src="bundle.js" type="text/javascript"></script>
</body>
</html>
When you need a particular module, that is not required on the initial load you can use
require.ensure(["module-a", "module-b"], function() {
var a = require("module-a");
// ...
});
That way it only gets loaded when you need it, thus decreasing your bundle size.
If you use routes and react-router you can use per route code splitting as described in this article
http://moduscreate.com/code-splitting-for-react-router-with-es6-imports/
Im my experience, typically with webpack-optimize-chunk-plugin, you split your projects code into a vendor.js and a bundle.js. like this:
module.exports = {
entry:{
vendor: ["react", "react-dom"], // list all vender libraries here
app: ['./path/to/entry.js']
},
output: {
path: path.join(__dirname, './public'),
filename:'bundle.js'
},
plugins: [
new webpack.optimize.OccurenceOrderPlugin(),
new webpack.optimize.CommonsChunkPlugin("vendor", "vendor.js")
]
}
So this would output a bundle.js and a vendor.js. I haven't seen webpack-optimize-chunk-plugin used in the way you described. (it would be very cool if possible).
Also I would check out all the other webpack optimization plugins to also help with the over all file size. (i.e. DedupePlugin, UglifyJsPlugin, OccurenceOrderPlugin...). More info here. Also here is an example of multi entry point that you may find helpful. Best of luck.