I have a Rails app, in which I built an embeddable widget. The setup is as follows:
app/views/widgets/review/show.js.erb:
<%
widget_data = @response_object.serializable_hash
answers = widget_data[:answers]
data = {
target_name: "",
content_identifier: "abc",
answers: answers.map do |answer|
{
# ... map answers
}
end
}
pack_url = asset_pack_path("review_widget.js")
public_path = Rails.root.join("public", pack_url.sub(/^//, ""))
compiled_js = File.read(public_path) if File.exist?(public_path)
css_url = asset_pack_path("review_widget.css")
public_css_path = Rails.root.join("public", css_url.sub(/^//, ""))
widget_css = File.read(public_css_path) if File.exist?(public_css_path)
safe_css = widget_css.to_s.gsub("r", "").gsub("n", "\n").gsub('"', '"')
%>
(function() {
<%# if Rails.env.production? %>
const style = document.createElement('style');
style.textContent = `
<%= safe_css %>
`;
document.head.appendChild(style);
<%# end %>
window.localizedStrings = <%= I18n.t("widgets.reviews", default: {}).to_json.html_safe %>;
window.reviewWidgetData = <%= data.to_json.html_safe %>;
<%= compiled_js.html_safe if compiled_js.present? %>
})();
app/javascript/packs/review_widget.ts:
import { initReviewWidget } from "../review_widget/base"
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initReviewWidget);
} else {
initReviewWidget();
}
app/javascript/review_widget/base.ts
import '../styles/review_widget.scss';
// more functions ...
export function initReviewWidget() {
// call some functions etc...
console.log('widget initialized');
}
HTML file for testing purposes:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Widget Test</title>
</head>
<body>
<h1>Testing the Widget</h1>
<!-- The widget will be injected into the body -->
<div id="#our-unique-widget-id"></div>
<script type="text/javascript">
(function() {
const script = document.createElement('script');
script.src = "http://localhost:5006/widgets/reviews.js?content_identifier=30a4d762e86596005b00b66c7b252fb4&locale=en";
script.async = true;
document.head.appendChild(script);
})();
</script>
</body>
</html>
Local server is running at localhost:5006 and server the content at /widgets/reviews.js.
If the <div id="#our-unique-widget-id"></div> <script...> are in a page/file that are part of the same rails server, the JS works and a widget is rendered.
If we have this deployed to our staging server, where instead of localhost:5006 we set it to a CloudFront instance URL (which gets reviews.js from the staging server), it works too.
However, when the script is embedded anywhere that is not the server where it’s served from, it does not work.
This is the response of the reviews.js?... request:
(function() {
const style = document.createElement('style');
style.textContent = `
:root {
}
`;
document.head.appendChild(style);
window.userDefinedFonts = [];
window.localizedStrings = {
"reviews_plural": "reviews",
"button_write_review": "Leave a review"
};
window.reviewWidgetData = {
// ... review data json
};
"use strict";
(self["webpackChunkour_app_name"] = self["webpackChunkour_app_name"] || []).push([["review_widget"], {
/***/
"./app/javascript/packs/review_widget.ts": /*!***********************************************!*
!*** ./app/javascript/packs/review_widget.ts ***!
***********************************************/
/***/
(function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
__webpack_require__.r(__webpack_exports__);
/* harmony import */
var _review_widget_base__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../review_widget/base */
"./app/javascript/review_widget/base.ts");
/* provided dependency */
var __react_refresh_utils__ = __webpack_require__(/*! ./node_modules/@pmmmwh/react-refresh-webpack-plugin/lib/runtime/RefreshUtils.js */
"./node_modules/@pmmmwh/react-refresh-webpack-plugin/lib/runtime/RefreshUtils.js");
__webpack_require__.$Refresh$.runtime = __webpack_require__(/*! ./node_modules/react-refresh/runtime.js */
"./node_modules/react-refresh/runtime.js");
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', _review_widget_base__WEBPACK_IMPORTED_MODULE_0__.initReviewWidget);
} else {
(0,
_review_widget_base__WEBPACK_IMPORTED_MODULE_0__.initReviewWidget)();
}
var $ReactRefreshModuleId$ = __webpack_require__.$Refresh$.moduleId;
var $ReactRefreshCurrentExports$ = __react_refresh_utils__.getModuleExports($ReactRefreshModuleId$);
function $ReactRefreshModuleRuntime$(exports) {
if (false) {
var testMode, errorOverlay;
}
}
if (typeof Promise !== 'undefined' && $ReactRefreshCurrentExports$ instanceof Promise) {
$ReactRefreshCurrentExports$.then($ReactRefreshModuleRuntime$);
} else {
$ReactRefreshModuleRuntime$($ReactRefreshCurrentExports$);
}
/***/
}
),
/***/
"./app/javascript/review_widget/base.ts": /*!**********************************************!*
!*** ./app/javascript/review_widget/base.ts ***!
**********************************************/
/***/
(function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
__webpack_require__.r(__webpack_exports__);
/* harmony export */
__webpack_require__.d(__webpack_exports__, {
/* harmony export */
initReviewWidget: function() {
return /* binding */
initReviewWidget;
}/* harmony export */
});
/* harmony import */
var _styles_review_widget_scss__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../styles/review_widget.scss */
"./app/javascript/styles/review_widget.scss");
/* provided dependency */
var __react_refresh_utils__ = __webpack_require__(/*! ./node_modules/@pmmmwh/react-refresh-webpack-plugin/lib/runtime/RefreshUtils.js */
"./node_modules/@pmmmwh/react-refresh-webpack-plugin/lib/runtime/RefreshUtils.js");
__webpack_require__.$Refresh$.runtime = __webpack_require__(/*! ./node_modules/react-refresh/runtime.js */
"./node_modules/react-refresh/runtime.js");
// more functions ...
function initReviewWidget() {
// call some functions etc...
// Select element #our-unique-widget-id and add some content...
console.log('widget initialized');
}
var $ReactRefreshModuleId$ = __webpack_require__.$Refresh$.moduleId;
var $ReactRefreshCurrentExports$ = __react_refresh_utils__.getModuleExports($ReactRefreshModuleId$);
function $ReactRefreshModuleRuntime$(exports) {
if (false) {
var testMode, errorOverlay;
}
}
if (typeof Promise !== 'undefined' && $ReactRefreshCurrentExports$ instanceof Promise) {
$ReactRefreshCurrentExports$.then($ReactRefreshModuleRuntime$);
} else {
$ReactRefreshModuleRuntime$($ReactRefreshCurrentExports$);
}
/***/
}
),
/***/
"./app/javascript/styles/review_widget.scss": /*!**************************************************!*
!*** ./app/javascript/styles/review_widget.scss ***!
**************************************************/
/***/
(function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
__webpack_require__.r(__webpack_exports__);
// extracted by mini-css-extract-plugin
/***/
}
)
}, /******/
function(__webpack_require__) {
// webpackRuntimeModules
/******/
var __webpack_exec__ = function(moduleId) {
return __webpack_require__(__webpack_require__.s = moduleId);
}
/******/
__webpack_require__.O(0, ["vendors-node_modules_pmmmwh_react-refresh-webpack-plugin_lib_runtime_RefreshUtils_js-node_mod-861caa"], function() {
return __webpack_exec__("./node_modules/@pmmmwh/react-refresh-webpack-plugin/client/ReactRefreshEntry.js"),
__webpack_exec__("./node_modules/@pmmmwh/react-refresh-webpack-plugin/client/ErrorOverlayEntry.js?sockProtocol=http"),
__webpack_exec__("./app/javascript/packs/review_widget.ts");
});
/******/
var __webpack_exports__ = __webpack_require__.O();
/******/
}
]);
//# sourceMappingURL=review_widget.js.map
}
)();
Any console.log('') I add in show.js.erb gets logged in the console, logging I add in the .ts files does not. With breakpoints in the .js response I can get to (self["webpackChunkour_app_name"] = self["webpackChunkour_app_name"] || []).push([["review_widget"], {, but not anything after that.
config/webpack/environment.js:
const path = require('path');
const { environment } = require('@rails/webpacker')
const webpack = require('webpack');
const customConfig = {
optimization: {
sideEffects: false
},
resolve: {
alias: {
'@components': path.resolve(__dirname, '../../app/javascript/components'),
'@utils': path.resolve(__dirname, '../../app/javascript/utils'),
},
},
};
environment.config.merge(customConfig);
environment.loaders.prepend('sass', require('./loaders/sass'))
environment.loaders.append('typescript', {
test: /.tsx?$/,
use: [
{
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-react'],
},
},
'ts-loader',
],
exclude: /node_modules/
});
environment.plugins.append('DefinePlugin', new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
}));
module.exports = environment
config/webpack/development.js:
process.env.NODE_ENV = process.env.NODE_ENV || 'development'
const webpackConfig = require('./base')
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin')
if (!webpackConfig.plugins) {
webpackConfig.plugins = [];
}
if (process.env.NODE_ENV === 'development') {
webpackConfig.plugins.push(new ReactRefreshWebpackPlugin())
}
const finalWebpackConfig = {
...webpackConfig,
devServer: {
...webpackConfig.devServer,
hot: true,
liveReload: false,
},
};
// Log the final Webpack configuration
console.log('Webpack configuration:', JSON.stringify(finalWebpackConfig, null, 2))
module.exports = finalWebpackConfig
config/webpack/base.js:
const { webpackConfig, merge } = require('@rails/webpacker')
const webpack = require('webpack')
const customConfig = require('./custom')
webpackConfig.plugins.push(
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
jquery: 'jquery',
})
)
module.exports = merge(webpackConfig, customConfig)
The output .js seems to include:
./app/javascript/packs/review_widget.ts
./app/javascript/review_widget/base.ts
- etc.
I am not sure anymore what the issue can be. Looking at the compiled js it looks like it’s defining functions but never calling it. Or that it uses relative (to project) paths that of course don’t work outside of that.
The idea is that this can be embedded on external websites.