Search code examples
djangovue.jsstatic-fileshtml-webpack-plugin

Django with vue.js frontend - Static Files Path


I am attempting to serve a django app with a vue frontend and need some help configuring the static files.

The Question TLDR:

How do I get Django to recognize this built path as an attempt to reach a static file, or alternatively, how do I modify the post-build injection path on the Vue side to match the current Django static-file settings?

Django serves the built index.html from vue, however it can not find the static files (scripts/css) that are autoinjected from the build process due to the path being "absolute". I have modified the vue.config.js to not autoinject the scripts (since they will need {% static %} during the build, and the template index.html to add them in appropriately.

My directory structure is as follows:

- Root
  - app
  - migrations
  - templates
    - app (outputDir)
      - index.html (Built from vue)
      - base.html
      - templateFolder1
      - templateFolder2
  - various .py files
  - assets (assetsDir)
    - css
      - static files
    - js
      - static files
  - frontend
    - public
      - index.html (Template for build for vue)
    - src
    - assets
    - components
    - App.vue
    - main.js

The build is run from the frontend directory, with --no-clean to not delete my django templates folder on build.

Here is my workaround for adding {% static %} tags to the built index.html. I realize I am breaking the convention vue has that assetsDir is a subdirectory of outputDir, and I am not opposed to adding another staticfile dir to the settings.py to match the convention (although my issue is still the same).

vue.config.js

publicPath: isProd ? '/app/' : '/',
outputDir: __dirname + '/../app/templates/app',
assetsDir: "../../../assets",
indexPath: './index.html',
configureWebpack: {
        ...
        plugins: [
            new HtmlWebpackPlugin({
                template: __dirname + '/public/index.html',
                favicon: __dirname + '/../assets/img/favicon/favicon.ico',
                inject: false,
                minify: false
            })
        ],
    },

public/index.html

{% load static %}
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>MAPP Remote</title>
      <link rel="shortcut icon" href="{% static '<%= htmlWebpackPlugin.files.favicon %>' %}">
      <% for (key in htmlWebpackPlugin.files.css) { %> <link rel="stylesheet" href="{% static '<%= htmlWebpackPlugin.files.css[key] %>' %}"> <% } %>
  </head>
    <body>
    ...
    <div id="app"></div>

    <!-- built files will be auto injected -->
  <% for (key in htmlWebpackPlugin.files.js) { %> <script type="text/javascript" src="{% static '<%= htmlWebpackPlugin.files.js[key] %>' %}"></script> <% } %>
  </body>
</html>

The built index.html:

app/templates/app/index.html

{% load static %}
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>MAPP Remote</title>
      <link rel="shortcut icon" href="{% static '/app/favicon.ico' %}">
       <link rel="stylesheet" href="{% static '/app/../../../assets/css/chunk-vendors.0ba3e87d.css' %}">  <link rel="stylesheet" href="{% static '/app/../../../assets/css/app.fb012fc8.css' %}"> 
  </head>
  <body>
    ...
    <div id="app"></div>

    <!-- built files will be auto injected -->
   <script type="text/javascript" src="{% static '/app/../../../assets/js/chunk-vendors.6a3b11f1.js' %}"></script>  <script type="text/javascript" src="{% static '/app/../../../assets/js/app.45295baa.js' %}"></script> 
  </body>
</html>

My django settings.py configuration for static files:

Settings.py

...
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
STATIC_URL = "/static/"
STATICFILES_DIRS = [os.path.join(BASE_DIR, "assets")]
STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles")
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
...

The way my static file finders are configured via Django requires modification to the built script/css paths inside of app/templates/app/index.html.

In stead of <script src="{% static '/app/../../../assets/js/chunk-vendors.6a3b11f1.js' %}">

The path currently needs to be <script src="{% static 'js/chunk-vendors.6a3b11f1.js' %}">

Changing the assetsDir path in vue.config.js to match the Vue convention of having assets be a subdirectory of outputDir results in a similar issue in which the path is 'app/assets/js/...' rather than 'js/...'


Solution

  • I decided to adjust the path that is loaded into the template during the Vue build by modifying public/index.html file as well as the vue.config.js options. I declared a const asset_dir = '/asset/dir in vue.config.js and then added this as an extra option to the HtmlWebpackPlugin to pull it into the template. Lastly, I substring the path for the static files by the length of the unnecessary portion of the path.

    vue.config.js

    const asset_dir = "../../../assets"
    
    module.exports = {
        publicPath: isProd ? '/app/' : '/',
        outputDir: __dirname + '/../app/templates/app',
        assetsDir: asset_dir,
        indexPath: './index.html',
        configureWebpack: {
            ...
            plugins: [
                new HtmlWebpackPlugin({
                    template: __dirname + '/public/index.html',
                    inject: false,
                    minify: false,
                    assetsDir: asset_dir
                })
            ],
        }
    }
    

    public/index.html

    {% load static %}
    <% if (htmlWebpackPlugin.options['assetsDir'] !== undefined) { %> <% var assetDirLength = htmlWebpackPlugin.options['assetsDir'].length + htmlWebpackPlugin.files.publicPath.length + "/".length%> <% } %>
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width,initial-scale=1.0">
        <title>MAPP Remote</title>
          <link rel="shortcut icon" href="{% static 'img/favicon/favicon.ico' %}">
          <% for (key in htmlWebpackPlugin.files.css) { %> <link rel="stylesheet" href="{% static '<%= htmlWebpackPlugin.files.css[key].substr(assetDirLength) %>' %}"> <% } %>
      </head>
      <body>
        <div id="app"></div>
        <!-- built files will be auto injected -->
        <!-- Pulled from htmlWebpackPlugin Docs -->
        <!-- https://github.com/jaketrent/html-webpack-template/blob/86f285d5c790a6c15263f5cc50fd666d51f974fd/index.html -->
        <% for (var chunk in htmlWebpackPlugin.files.chunks) { %>
          <script src="{% static '<%= htmlWebpackPlugin.files.chunks[chunk].entry.substr(assetDirLength) %>' %}"></script>
        <% } %>
    
      </body>
    </html>