Guide 03 · Developer docs

The theme repo and the asset pipeline

Everything a developer changes on a Core dna site lives in its theme repository. This guide covers the repository and its build, printing records in templates, stylesheets and client-side code, and the parts of a site around the main content.

L1–L2
Unit 04 · Client-side code

jQuery, loaded once

Where a site uses jQuery, there must be exactly one copy on the page. The arrangement that guarantees it (a global loaded once and marked external to webpack), the common configuration that bundles it twice without anyone noticing, how to check your build, and the rule for new code.

Who this is for. The developer working in a repository that uses jQuery, or adding a jQuery plugin. Afterwards you can tell how jQuery reaches the page, find out whether the build ships it twice, and avoid the plugin bugs a second copy causes.

The arrangement that works: a global, marked external

A production gulp repository loads jQuery once, as a plain script before its bundles, and tells webpack that jquery is that global rather than a module to bundle:

JavaScript config/webpack.js (extract)
externals: {
  jquery: 'jQuery'
},
plugins: [
  new ProvidePlugin({ $: 'jquery', jQuery: 'jquery' })
]
Template code The base template
<script src="/static/vendor/jquery-3.6.0.min.js" nonce="{$_inlineScriptNonce}"></script>
<script src="/static/js/components.bundle.js?v={$_currentSite->getLastVcsHash()}" nonce="{$_inlineScriptNonce}" defer></script>
<script src="/static/vendor/jquery-3.6.0.min.js" nonce="{{ _inlineScriptNonce }}"></script>
<script src="/static/js/components.bundle.js?v={{ _currentSite.getLastVcsHash() }}" nonce="{{ _inlineScriptNonce }}" defer></script>

With externals, an import $ from 'jquery' anywhere in the source resolves to window.jQuery, and ProvidePlugin does the same for code that uses $ without importing it. Nothing is bundled. The global must be loaded before any bundle that uses it, and without defer or async, so that it is there when they run.

The consequence for your code: never import your own copy (import $ from 'jquery/dist/jquery', or a package that bundles jQuery), and register plugins on the global.

The configuration that ships it twice

Several production webpack repositories have a vendor entry containing jQuery and a ProvidePlugin for $, and no externals and no shared chunks:

JavaScript webpack.common.js (extract)
entry: {
  app: ['./src/app/js/index.js', './src/app/scss/style.scss'],
  vendor: ['jquery']
},
plugins: [
  new webpack.ProvidePlugin({ $: 'jquery', jQuery: 'jquery', jquery: 'jquery' })
]

Each entry is built on its own, so vendor.js contains jQuery and app.js, wherever ProvidePlugin supplies $, contains a second copy. Both are loaded on every page. On one site that is about 31 KB gzipped of duplicate code; worse, there are now two jQuery objects, and a plugin registered on one is invisible to code using the other.

Fix it with externals and one script tag, as above, or by letting webpack share the module between entries with optimization.splitChunks or an entry's dependOn.

Check your build

webpack writes the licence headers of what it bundled into *.LICENSE.txt files beside the output. More than one of them naming jQuery means more than one copy:

Shell Count the copies of jQuery in the build
grep -l "jQuery JavaScript Library" dist/*.LICENSE.txt
# dist/app.js.LICENSE.txt
# dist/vendor.js.LICENSE.txt   <- two copies

In the browser, jQuery.fn.jquery in the console gives the version of the global, and a plugin that is "not a function" right after it was registered is the symptom of a second copy.

New code does not add jQuery

Write new features in plain JavaScript, even in a repository that loads jQuery: the DOM and fetch cover what jQuery was used for, and every new dependency on it makes it harder to remove. The newest production repositories use none, and one states the rule in its webpack configuration. Existing jQuery code can stay until the feature is rewritten for other reasons.

Go deeper

Was this page helpful?

Your answer goes to the docs team.