Set up Webpack for .ejs - javascript

I have project where I use ejs files. Only problem that I use ejs files for server side. Code below. I need set up webpack in way, that on fly it insert in index.ejs <script src="frontend/build/..."></script> and <style src="frontend/build/..."/>. Only that I know is that I should use webpack-middleware somehow. If someone have experience, please help me set up.
// webpack.config.js
import path from 'path';
import ExtractTextPlugin from 'extract-text-webpack-plugin';
import CleanWebpackPlugin from 'clean-webpack-plugin';
const inProduction = process.argv[process.argv.length - 1]
.match(/[a-z]+$/g)[0] === 'production';
const basic = {
entry: {
app: path.join(__dirname, 'frontend/source/scripts/main.js'),
},
output: {
path: path.join(__dirname, 'frontend/build'),
filename: '[name].[chunkhash].js',
},
};
const module = {
rules: [{
test: /\.css$/,
use: ExtractTextPlugin.extract({
use: ['css-loader'],
}),
},
{
test: /\.js$/,
use: ['babel-loader'],
exclude: ['/node_modules'],
},
],
};
const plugins = [
new ExtractTextPlugin('[name].[contenthash].css'),
new CleanWebpackPlugin('build'),
];
export default {
...basic,
module,
plugins,
};
<!-- index.ejs -->
<header class="header">Git Rendering</header>
<main class="container">
<% if (branches) { %>
<%- include('branches'); %>
<% } %>
<% if (commits) { %>
<%- include('commits'); %>
<% } %>
<% if (files) { %>
<%- include('files'); %>
<% } %>
<% if (file) { %>
<%- include('file'); %>
<% } %>
</main>

The easiest solution would be to load the files with html-webpack-plugin. You can write templates and inject your script tags.
If you just want to add your webpack entries, it will already happen automatically.
The only thing you need to watch out for is, that html-webpack-plugin uses ejs templating as well. So you will need to change either your or the plugins templating syntax.

Related

How to exec javascript code only when a plugin finished the execution and exec another plugin only when this js code finished asynchronous reading

How do I reference the hashed image (in the html page in the dist folder) after it has been created with copy-webpack-plugin?
I tried to resolve this problem in this way.
I have in my index.ejs file (you can consider this the .html file) a classic tag <img> that I'm copying in the dist folder with copy-webpack-plugin
My problem is that in 'production' mode I add an hash to my image instead in the index.ejs file the attribute src of the <img> still will point to the image without the hash. Thus my index.html in the dist folder doesn't display the image.
In order to resolve this problem I used WebpackManifestPlugin to generate a manifest.json that map my images and corresponding webpack output images (with hash) in a object like this:
{
"assets/img/natura.jpg": "./assets/img/natura.e1b203dd72abf2858773.jpg",
"assets/img/natale.jpg": "./assets/img/natale.5955e3731fd0538bb5ec.jpg",
"assets/img/logo-angular.svg": "./assets/img/logo-angular.e7d82ae6d37ff090ba95.svg",
"assets/img/manifest.json": "./assets/img/manifest.1473edc04cb44efe5ce6.json"
}
Later I have generated the manifest.json I can read this file:
productsJSON = require('./assets/img/manifest.json');
and finally I can pass productsJson to my index.ejs in this way:
new HtmlWebpackPlugin({
inject: false,
template: "./index.ejs", //Puoi mettere anche un file html
templateParameters: {
'myJSON': productsJSON
},
minify:true
})
and in the index.ejs I can do:
<img
src="<%= (process.env.NODE_ENV === 'production') ? myJSON['assets/img/natura.jpg'] : 'assets/img/natura.jpg' %>"
alt="Natura.jpg"
/>
<img
src="<%= (process.env.NODE_ENV === 'production') ? myJSON['assets/img/natale.jpg'] : 'assets/img/natale.jpg' %>"
alt="Natale.jpg"
/>
These are the whole files above:
index.ejs
<!DOCTYPE html>
<html>
<head>
<title>Custom insertion example</title>
<!-- prettier-ignore -->
<% if (process.env.NODE_ENV === 'production'){%>
<% for(var i=0; i < htmlWebpackPlugin.files.css.length; i++) {%>
<link
type="text/css"
rel="stylesheet"
href="<%= htmlWebpackPlugin.files.css[i] %>"
/>
<% } }%>
</head>
<body>
<img
src="<%= (process.env.NODE_ENV === 'production') ? myJSON['assets/img/natura.jpg'] : 'assets/img/natura.jpg' %>"
alt="Natura.jpg"
/>
<img
src="<%= (process.env.NODE_ENV === 'production') ? myJSON['assets/img/natale.jpg'] : 'assets/img/natale.jpg' %>"
alt="Natale.jpg"
/>
<button class="hello-world-button">Ciao</button>
<img id="asset-resource" />
<% for(var i=0; i < htmlWebpackPlugin.files.js.length; i++) {%>
<script
type="text/javascript"
src="<%= htmlWebpackPlugin.files.js[i] %>"
></script>
<% } %>
</body>
</html>
webpack.production.config.js
const pathM = require('path');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
/* const { CleanWebpackPlugin } = require('clean-webpack-plugin');
*/
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { WebpackManifestPlugin } = require('webpack-manifest-plugin');
let productsJSON={};
try {
productsJSON = require('./assets/img/manifest.json');
}
catch (err) {
console.log(`Alla prima esecuzione il file manifest.json non esiste. Fare il build
due volte. Sarebbe meglio gestirlo in un altro modo. Ma non so
temporizzare l'esecuzione dei plugin. Cioè HTMLWebPackPlugin andrebbe
eseguito solo dopo che ManifestPlugin ha creato Manifest.json `)
}
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/bundle.[contenthash].js',
path: pathM.resolve(__dirname, './dist'),
assetModuleFilename: '[path][name].[contenthash][ext]',
publicPath: './',
clean: true /* {
dry: false,
keep:/\.css$/
} */ //Serve per cancellare la cartella dist dalla precedente esecuzione
},
mode: 'production',
module: {
rules:[
{
test: /\.(png|jpe?g|webp|avif|gif|svg)$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 3 * 1024 //3 Kilobytes QUI CAMBIA LA SOGLIA A 3 KByte
/* Il logo Angular è 6, 5 Kbyte.Cambia la soglia per includere
nel bundle js il logo */
}
}
},
/*rules per quando provi ad importare un file css da javascript. Uso due loaders
css-loader legge il contenuto del css e ritorna il contenuto
style-loader prende il css e lo mette nella pagina, mette il css proprio nel
bundle.js Poi vedremo come generarli come file separati
*/
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader,'css-loader']
},
{
test: /\.scss$/,
use: [MiniCssExtractPlugin.loader,'css-loader','sass-loader']
},
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [['#babel/env', {
targets: "> 0.1%, not dead",
debug:true,
useBuiltIns: 'usage',
//Puoi mettere anche solo version:3
//La versione la puoi prelevare da package.json
corejs:{version:3.26 , proposals:true}
}]],
//plugins: ['#babel/plugin-proposal-class-properties']
}
}
}
]
},
plugins: [
new CopyWebpackPlugin({
patterns: [
{
from: './assets/img', to: './assets/img/[name].[contenthash][ext]',
globOptions: {
ignore: [
// Ignore all `txt` files
"**/*.json",
],
}, },
],
options: {
concurrency: 100,
},
}),
new WebpackManifestPlugin({
//Percorso assoluto che serve per dire dove mettere il file manifest.json
fileName: pathM.resolve(__dirname, 'assets/img/manifest.json'),
/* publicPath: '/dist/' QUesto metterebbe /dist/ prima del percorso di sopra*/
filter: (file) => { const regEx = /img/;
return regEx.test(file.name);
},/*map: (file) => {
if ('production' === env.NODE_ENV) {
// Remove hash in manifest key
file.name = file.name.replace(/(\.[a-f0-9]{32})(\..*)$/, '$2');
}
return file;
},*/
}),
new MiniCssExtractPlugin({
filename:"css/main.[contenthash].css"
}),
new HtmlWebpackPlugin({
inject: false,
template: "./index.ejs", //Puoi mettere anche un file html
templateParameters: {
'myJSON': productsJSON
},
minify:true
})
/*Nella seguente configurazione di questo plugin eliminiamo tutti i file
e le cartelle e sottocartelle a partire dalla cartella .dist che è quella
specificata in ouput.path
asteriscoasterisco/asterisco vuol dire tutti i file e le sottocartelle
Inoltre specifico di ripulire anche tutti i file e le sottocartelle
dentro la cartella nomeCartella
Nota che devo fornire un percorso assoluto perchè di default parte
da ./dist (impostata in output.path)
*/
//new CleanWebpackPlugin({
// cleanOnceBeforeBuildPatterns: [
// '**/*',
// path.join(process.cwd(),'nomeCartella/**/*')
//]
//})
]
}
I have two problems related.
I have to exec the instruction productsJSON = require('./assets/img/manifest.json'); only when WebpackManifestPlugin generated manifest.json
And I have to synchronize plugin execution, namely exec HtmlWebpackPlugin only when the file manifest.json is already ready
Here the whole project:
https://github.com/cuccagna/Webpack28HandleHashWithManifest
How could I do?
I followed the a suggest in an answer.
Using the done hook. (but in this way doesn't work because the html-webpack-plugin isn't executed after the hook,so this step is missing)
new WebpackManifestPlugin({
fileName: pathM.resolve(__dirname, 'assets/img/manifest.json'),
filter: (file) => { const regEx = /img/;
return regEx.test(file.name);
},
apply(webpackCompiler){
webpackCompiler.hooks.done.tap('WebpackManifestPlugin', (stats) => {
productsJSON = require('./assets/img/manifest.json');
})}
},
)
Here we are the documentation of manifest-webpack-plugin https://www.npmjs.com/package/webpack-manifest-plugin/v/5.0.0
and in the hook section you talk about to syncronize the order of execution of plugins but I don't understand how I can use it in an effective way
Not sure if my answer will appeal to you. Just wanted to suggest an alternative approach. Sometimes, what a plugin does is pretty trivial, yet configuring it in webpack can get a bit messy.
An alternative approach is to use webpack primarily for the really hard job of treeshaking. I have then found this event very useful for writing Node.js code afterwards:
plugins:[
{
apply: (compiler) => {
compiler.hooks.afterEmit.tap('AfterEmitPlugin', () => {
doCustomWork();
});
}
}
]
I used this custom code for a demo app of mine to produce some finishing touches and produce HTML like this, with the readability I wanted.
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width, initial-scale=1, shrink-to-fit=no'>
<base href='/spa/' />
<title>OAuth Demo App</title>
<link rel='stylesheet' href='bootstrap.min.css?t=1673180480701' integrity='sha256-Pxxy6CTJX1fLPROtVJ8Y5mSFIhoXWjOHfzRgUaTTAsI='>
<link rel='stylesheet' href='app.css?t=1673180480701' integrity='sha256-B7pu+gcFspulW4zXfgczVtPcEuZ81tZRFYeRciEzWro='>
</head>
<body>
<div id='root' class='container'></div>
<script type='module' src='vendor.bundle.js?t=1673180480701' integrity='sha256-X/U647yXWbmD0b7NMVorHIGHHHW3KN7pohL5PXr4gdw='></script>
<script type='module' src='app.bundle.js?t=1673180480701' integrity='sha256-amOGxWiYqyf4cHKy7FJ49RWxJSQR7w0yg4lHUKFYf3Y='></script>
</body>
</html>
You don't need WebpackManifestPlugin nor CopyWebpackPlugin, HtmlWebpackPlugin with .ejs template is fully capable to complete the task on its own.
You just need to use the correct template syntax:
<img src="<%= require('./assets/img/natura.jpg') %>" alt="Natura.jpg" />
<img src="<%= require('./assets/img/natale.jpg') %>" alt="Natale.jpg" />
Looks like you need some explanation besides a solution. So here we go.
The idea about webpack is that, it's a bundler. It finds assets by following all the require/import hints in your code, starting from the "entry points" that you supply. This process is very akin to how web crawler works.
How the image got included even without CopyWebpackPlugin? It's because <%= require('./assets/img/natura.jpg') %> is a legit require hint, webpack sees that and just includes the image into its compilation process.
Next step normally involves loader, but in webpack v5 they introduce asset-modules, I see you're using it in your webpack config. Basically it's a built-in loader for static assets.
output: {
assetModuleFilename: '[path][name].[contenthash][ext]',
// ...
module: {
rules:[{
test: /\.(png|jpe?g|webp|avif|gif|svg)$/,
type: 'asset',
To answer your questions in comment:
I can't figure how is automatically added the hash to images.
Well, it's because you have specified assetModuleFilename to include [contenthash]. Basically asset modules has already done the job of CopyWebpackPlugin for you.
how can I optimize images?
You can chain the ImageMinimizerPlugin.loader with asset modules. Please read the doc for more details. The idea be like:
rules: [
{
test: /\.(jpe?g|png|gif|svg)$/i,
type: "asset",
},
{
test: /\.(jpe?g|png|gif|svg)$/i,
use: [
{
loader: ImageMinimizerPlugin.loader,
And finally, you might wonder when will CopyWebpackPlugin be useful?
Remember the web-crawler-like behavior I mentioned above? It also implies, if some assets is not reachable by following require/import hints, then webpack is completely unaware of them, and they won't be included in /dist. This is where CopyWebpackPlugin comes to rescue by simply copying these forgotten assets over to the other side.
Now you know what's what, feel free to use CopyWebpackPlugin where you see fit.
Update
So I looked into ManifestPlugin's source code. And I found a hack:
webpack.production.config.js
// 👇 You don't need to require it.
// productsJSON = require("./assets/img/manifest.json");
// 👇 But you do need an empty object
// we'll populate its content later
const productsJSON = {};
module.exports = {
// ...
plugins: [
new WebpackManifestPlugin({
// ...
generate(seed, files, entrypoints) {
const manifestJson = files.reduce(
(manifest, file) => Object.assign(manifest, { [file.name]: file.path }),
seed
);
// 👇 here's the magic,
// I just hijack the result right inside JS
// so we don't need to wait for it to be written to disk
// Then just Object.assign it onto productsJson
Object.assign(productsJSON, manifestJson);
console.log('tada!', productsJSON);
return manifestJson;
},
}),
new HtmlWebpackPlugin({
inject: false,
template: "./index.ejs",
templateParameters: {
myJSON: productsJSON,
},
minify: true,
}),
],
};
It's a possible solution. But I'm not gonna test it for you this time. I've already deleted your repo from my laptop. And I really feel reluctant do it again, because you include the whole /node_modules folder into the repo. It takes forever to download.
Theoretically this could work. But still it depends on the execution order of WebpackManifestPlugin and HtmlWebpackPlugin. If WebpackManifestPlugin comes first then we got luck, problem solved. But I'm not completely sure about it.
Give it a try and may the force be with you!

Easy Auto-complet is not recognized

I would to use easy-autocomplete to add suggestions while my users are typing the pseudo of a player on my web-app.
But it doesn't work.
Here is the message I've got :
people.js:7 Uncaught TypeError: $input.easyAutocomplete is not a function
at HTMLDocument.<anonymous> (people.js:7)
at Object../node_modules/turbolinks/dist/turbolinks.js.e.dispatch (turbolinks.js:75)
at r.notifyApplicationAfterPageLoad (turbolinks.js:994)
at r.pageLoaded (turbolinks.js:948)
at turbolinks.js:872
Here is my code :
In app/javascript/packs/people.js
document.addEventListener("turbolinks:load", function() {
$input = $("[data-behavior='autocomplete']")
var options = {
getValue: "pseudo"
}
$input.easyAutocomplete(options)
});
console.log("custom js file loaded")
Here is my layout :
app/views/layouts/application.html.erb
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<script src="//code.jquery.com/jquery-1.11.2.min.js"></script>
<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload', defer: true %>
<%= javascript_pack_tag 'people', 'data-turbolinks-track': 'reload' %>
Of course in my layout, before and after this part there are script for OG facebook and twitter balises
Here is my environment.js :
const { environment } = require('#rails/webpacker')
const webpack = require('webpack')
environment.plugins.prepend('Provide',
new webpack.ProvidePlugin({
$: 'jquery/src/jquery',
jQuery: 'jquery/src/jquery'
})
)
module.exports = environment
I also tried this but it did not work as well :
const { environment } = require('#rails/webpacker')
const webpack = require('webpack');
// Preventing Babel from transpiling NodeModules packages
environment.loaders.delete('nodeModules');
// Bootstrap 4 has a dependency over jQuery & Popper.js:
environment.plugins.prepend('Provide',
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
Popper: ['popper.js', 'default']
})
);
module.exports = environment
If you need more information I'm ready to answer.
Thanks by advance
You are including the javascript file in wrong way.
As the helper method doc say:
# DO:
#
# <%= javascript_pack_tag 'calendar', 'map' %>
#
# DON'T:
#
# <%= javascript_pack_tag 'calendar' %>
# <%= javascript_pack_tag 'map' %>
https://github.com/rails/webpacker/blob/f1b06c7fd5bc4b7fc7128742d1078466b94af71f/lib/webpacker/helper.rb#L89-L97
In your case:
<%= javascript_pack_tag 'application', 'people', 'data-turbolinks-track': 'reload', defer: true %>

Code Mirror not rendering correctly on Heroku Rails 6 app

I recently added the code mirror editor to my rails 6 app. I was able to get everything working fine on my local environment (ubuntu 18.04), but when I deploy the app to heroku the code mirror editor renders the actual text editing area far below the div. It also seems to be filling it with x's. here is a screenshot of it. The x's near the top are actually part of the top editor not shown, and the lower x's are part of the main editor in the picture.
Here is my application.js file:
require("#rails/ujs").start()
require("turbolinks").start()
require("#rails/activestorage").start()
require("channels")
require("jquery")
import { autocompleteExport } from '../custom/autocomplete';
import { initialize } from '../custom/editor';
import { formatToc } from '../custom/page_display';
window.jQuery = $;
window.$ = $;
$(document).on("turbolinks:load", autocompleteExport.categories)
$(document).on("turbolinks:load", autocompleteExport.search)
$(document).on("turbolinks:load", autocompleteExport.admins)
$(document).on("turbolinks:load", initialize)
$(document).on("turbolinks:load", formatToc)
and here is the editor file that initializes the editors:
import CodeMirror from 'codemirror/lib/codemirror.js'
import 'codemirror/lib/codemirror.css'
import 'codemirror/mode/markdown/markdown.js'
function initialize(){
let textArea = document.getElementById('page_edit_content')
let editor = document.getElementById('content-editor')
if(textArea && !editor){
var contentEditor = CodeMirror.fromTextArea(textArea, {
lineWrapping: true,
mode: "markdown",
});
contentEditor.display.wrapper.id = "content-editor"
}
textArea = null
editor = null
textArea = document.getElementById('page_edit_summary')
editor = document.getElementById("summary-editor")
if(textArea && !editor){
var contentEditor = CodeMirror.fromTextArea(textArea, {
lineWrapping: true,
mode: "markdown",
});
contentEditor.display.wrapper.id = "summary-editor"
}
textArea = null
editor = null
textArea = document.getElementById('page_content')
editor = document.getElementById("new-content-editor")
if(textArea && !editor){
var contentEditor = CodeMirror.fromTextArea(textArea, {
lineWrapping: true,
mode: "markdown",
});
contentEditor.display.wrapper.id = "new-content-editor"
}
textArea = null
editor = null
textArea = document.getElementById('page_summary')
editor = document.getElementById("new-summary-editor")
if(textArea && !editor){
var contentEditor = CodeMirror.fromTextArea(textArea, {
lineWrapping: true,
mode: "markdown",
});
contentEditor.display.wrapper.id = "new-summary-editor"
}
}
export {initialize}
Lastly here is one of the views which is having the issue:
<% #page_title = "New Page" %>
<div class="container form-container">
<%= form_for #page, url: world_pages_path(params[:world_name]), html: {style: "width: 100%"} do |f| %>
<%= render 'shared/error_messages', errors: flash[:errors] %>
<div class="form-group">
<%= f.label :title %>
<%= f.text_field :title, autofocus: true, class: "form-control" %>
</div>
<div class="form-group">
<%= f.label :summary %>
<%= f.text_area :summary, class: "form-control" %>
</div>
<div class="form-group">
<%= f.label :content %>
<%= f.text_area :content, class: "form-control" %>
</div>
<div class="form-group">
<%= f.submit "Create!", class: "btn btn-primary" %>
</div>
<% if params[:category] %>
<%= hidden_field_tag :category, params[:category] %>
<% end %>
<% end %>
</div>
I'm pretty much at a loss for what could be causing this any help would be greatly appreciated.
edit:
After looking a bit through my browsers devtools, it is looking like the issue may be coming from the css not properly loading in my editor.js file, which is where I import the css files for code mirror.
edit-2:
When I remove the css import statement from the editor.js file, I get the same behavior in development, so I know for sure that that is the issue. If anyone knows the correct way to import styles from node modules that would be very helpful
If anyone runs into something like this in the future, I was able to find an answer.
The issue was stemming from my misunderstanding of webpack.
I was directly importing the css from the node module into the editor.js file. I am still not a webpack expert so I couldn't say why this worked locally, but it didn't properly set up the css files to be compiled by webpack in production.
The fix was to instead create an application.scss file in app/javascript/src. It was in this file I added #import 'codemirror/lib/codemirror.css';
After doing this I added import '../src/application.scss' to my application.js file so that webpack would know to compile the css.
Finally, I added <%= stylesheet_pack_tag 'application', 'data-turbolinks-track': 'reload' %> into my application.html.erb layout file which pulls in the css from the compiled files.

Rails Leaflet Webpack blank page

This a continuation of a related problem that #rossta fixed part of, but now the problem is moved so thought I'd start over.
No errors and the script is completing (verified by console.log outputs in the script). The body element shows up. This worked with gem leaflet and now webpack in Rails 5.2, but not now in Rails 6 with webpack
I moved the script into the page to isolate the problem map/index.html.erb
<p id="notice"><%= notice %></p>
<% provide(:title, 'Map') %>
<h4>This is map/index.html.erb and is put in layouts/map.html.erb.</h4>
<div id="map_two" class="map clearfix"></div> -->
<script>
function makeMapTwo() {
console.log('Hello from makeMapTwo in map/index.html.erb')
var mapVar = L.map("map_two", { center: [34.040951, -118.258579], zoom: 13 });
L.tileLayer('https://crores.s3.amazonaws.com/tiles/bkm/{z}/{x}/{y}.png').addTo(mapVar);
$.getJSON("line_data.geojson", function (data_data) {
var timelineData = L.timeline(data_data, {
style: function(data_data){
return {
stroke: true,
fillOpacity: 0.5
}
}, // end style: function(data_data)
waitToUpdateMap: true,
onEachFeature: function(data_data, layer) {
layer.bindTooltip(data_data.properties.popup, { direction: 'top' } );
} // onEachFeature:
}); // end let timelineData = L.timeline
var timelineControl = L.timelineSliderControl({
enableKeyboardControls: true,
steps: 100,
start: 1885,
end: 1928,
});
timelineData.addTo(mapVar);
timelineControl.addTo(mapVar);
timelineControl.addTimelines(timelineData);
}); // end $.getJSON
}; // end function makeMapTwo()
$(document).ready(function() {
makeMapTwo();
});
</script>
views/layouts/map.html.erb
<!DOCTYPE html>
<html>
<head>
<title><%= full_title(yield(:title)) %></title>
<h6>This is layouts/map.html.erb. A note to remind me that header is happening twice in maps TODO</h6>
<%= favicon_link_tag 'favicon.ico' %>
<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload', 'data-turbolinks-suppress-warning': true %>
<%= stylesheet_pack_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<style>
.map {
height: 400px;
width: 100%
}
</style>
<%= csrf_meta_tags %>
</head>
<%= render 'layouts/header' %> <!-- the navbar -->
<body class="container" data-mapbox-token="<%= ENV['MAPBOX_TOKEN'] %>">
<%= yield %>
<%= render 'layouts/footer' %>
</body>
</html>
and app/javascript/packs/application.js:
import "core-js/stable"
import "regenerator-runtime/runtime"
import '../stylesheets/application'
window.jQuery = $
window.$ = $
import 'leaflet'
import "leaflet.timeline"
require("#rails/ujs").start()
require("turbolinks").start()
require("#rails/activestorage").start()
require("trix")
require("#rails/actiontext")
require("jquery")
import "bootstrap"
import 'bootstrap/dist/js/bootstrap'
document.addEventListener("turbolinks:load", () => {
$('[data-toggle="tooltip"]').tooltip()
$('[data-toggle="popover"]').popover()
})
config/webpack/environment.js:
const { environment } = require('#rails/webpacker')
const webpack = require('webpack')
environment.plugins.append('Provide',
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
jquery: 'jquery',
Popper: ['popper.js' ,'default'],
}))
module.exports = environment
package.json
const { environment } = require('#rails/webpacker')
const webpack = require('webpack')
environment.plugins.append('Provide',
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
jquery: 'jquery',
Popper: ['popper.js' ,'default'],
}))
module.exports = environment
Debugging needed, but I'm not sure where to start.
I can't believe that this change had any effect but it did.
Changed the map_two in <div id="map_two"... var mapVar = L.map("map_two"... to just map and it loads. I was using map_two because when I was experimenting earlier I thought the two pages I was using with just map were getting confused. I also tried map-two and it didn't work either (not that I would have expected it, but I still do't understand what is happening. I restarted the server between the changes to be more sure of what is going on.

Why is webpack not exposing jQuery as a global?

My project requires a few npm modules, which I'm using yarn and webpack to load in. Namely, they are jquery, bootstrap3, moment, jquery-tablesort, jquery-ujs, bootstrap-select, and livestamp.
Some of those plugins need jQuery to be exposed as the global $ object. I've tried to make webpack do that, but so far I've failed.
My webpack config currently looks like this:
module.exports = {
entry: packPaths.reduce(
(map, entry) => {
const localMap = map;
const namespace = relative(join(entryPath), dirname(entry));
const key = join(namespace, basename(entry, extname(entry)));
localMap[key] = [resolve(entry)];
if (entry.includes('vendor')) {
localMap[key].unshift('babel-polyfill');
}
return localMap;
}, {}
),
output: {
filename: '[name].js',
path: output.path,
publicPath: output.publicPath
},
module: {
rules: [{
test: require.resolve('jquery'),
use: [{
loader: 'expose-loader',
options: '$'
}]
}, ...sync(join(loadersDir, '*.js')).map(loader => require(loader))]
},
plugins: [
new webpack.EnvironmentPlugin(JSON.parse(JSON.stringify(env))),
new ExtractTextPlugin(env.NODE_ENV === 'production' ? '[name]-[hash].css' : '[name].css'),
new ManifestPlugin({
publicPath: output.publicPath,
writeToFileEmit: true
})
],
resolve: {
extensions: settings.extensions,
modules: [
resolve(settings.source_path),
'node_modules'
]
},
resolveLoader: {
modules: ['node_modules']
}
};
On the advice in this question, I added the module section for expose-loader and removed a previous ProvidePlugin section that I had tried; neither of these options have worked.
My scripts are included like so (in application.html.erb):
<%= javascript_pack_tag 'vendor.js', 'data-turbolinks-track': 'reload' %>
<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
<%= javascript_include_tag "//code.highcharts.com/highcharts.js", "chartkick" %>
There are no other scripts included before or after these three. In vendor.js:
import * as $ from 'jquery';
window.$ = window.jQuery = $;
import 'bootstrap3'
import 'moment'
import 'jquery-tablesort';
import 'jquery-ujs';
import 'bootstrap-select';
import 'livestamp';
The manual assignment to window.$ also doesn't work. When Bootstrap loads, it gives me this error:
transition.js:59 Uncaught ReferenceError: jQuery is not defined
at Object.<anonymous> (transition.js:59)
at __webpack_require__ (bootstrap bde90632505934e68af9:19)
at Object.module.exports (npm.js:2)
at __webpack_require__ (bootstrap bde90632505934e68af9:19)
at Object.<anonymous> (vendor.js:1)
at __webpack_require__ (bootstrap bde90632505934e68af9:19)
at Object.<anonymous> (runtime.js:736)
at __webpack_require__ (bootstrap bde90632505934e68af9:19)
at bootstrap bde90632505934e68af9:65
at vendor.js:69
(Note the vendor.js in the last line of the trace there is the compiled vendor.js, i.e. not the one included above but the Webpack result.)
What can I do to fix this?
I think in this situation you should use require instead import
Try this in your vendor.js:
window.$ = window.jQuery = require('jquery');
require('bootstrap');
require('moment');
// others...

Categories