Webpack, typescript and angularjs: issue while creating new project - angularjs

I am new to webpack and typings, i am trying to setup new project.
I have globally installed the webpack.
Issues are
Even it is not allowing me to put the code of entrypoint.ts in a module like
module taxCalculator{
[.....entrypoint.ts code.......]
}
webpack build works fine when i avoid point 1 but i am not able to use the testClass in entrypoint.ts while creating mainPageController. when i load the application get following error in console:
taxCalculator.bundle.js:13307 ReferenceError: taxCalculator is not defined
In my entrypoint.ts vs 2013 shows the red error line below the 'require' text.
I have already referred a lot on internet but could not find anything and wasted 4-5 hours of mine, can someone please guide me what i am doing wrong.
Following are my files for reference.
Package.json
{
"name": "taxcalculator",
"version": "1.0.0",
"description": "To Calculate Tax",
"scripts": {
"build": ""
},
"author": "temp",
"license": "ISC",
"devDependencies": {
"ts-loader": "1.3.3",
"typescript": "2.0.0",
"typings": "2.1.0",
"webpack": "1.14.0"
},
"dependencies": {
"angular": "1.5.0"
}
}
tsconfig.json
{
"compilerOptions": {
"target": "es5"
}
}
typings.json
{
"dependencies": {
"angular": "registry:dt/angular#1.5.0+20170111164943"
}
}
entrypoint.ts
/// <reference path="../../typings/index.d.ts"/>
/// <reference path="TestClass.ts"/>
var angular = require('angular');
var app = angular.module('taxCalculatorApp', []);
app.controller("mainPageController", ["$scope", function(scope) {
scope.testString = "My String Value";
scope.myObj = new taxCalculator.TestClass("Constructor string");
}])
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf8"/>
<title>Tax Calculator</title>
</head>
<body>
<div ng-app="taxCalculatorApp">
<div ng-controller="mainPageController">
<div>{{testString}}</div>
<div>{{myObj.myString}}</div>
</div>
Loading.....!!
</div>
<script src="../dist/taxCalculator.bundle.js"></script>
</body>
</html>
testclass.ts
module taxCalculator {
export class TestClass {
constructor(public myString) {
}
}
}

In the compilerOptions section of your tsConfig file, add the following:
"moduleResolution" : "node", // or "classic"
"module" : "amd",
The moduleResolution section determines how the tsc compiler looks for modules. A full description can be found on the typescript docs.
The module section describes how each of your modules will be written out. By default this will be CommonJS, which is not what you want. It should probably be amd, which is a front-end friendly module system.
It's a good idea to take a deeper look at all the compiler options and see if any more are relevant for your project. In general, starting off new projects with strict null checks and no implicit any will help you in the long run.

Related

Capybara tests using headless chromium not reading React code

Problem
When running e2e tests with Capybara in my Rails/React app, whenever the javascript uses React, it has trouble executing the code. <div id="root"></div> remains empty while the code renders properly locally and in docker. I've duplicated this running the capybara tests locally as well. What is odd is that if I add a document.getElementById("root").innerText = "Foo bar" it runs the javascript and either doesn't know how to execute the ReactDOM.render bit or just doesn't. When running tests against Stimulus code, it renders properly. For funsies, I downgraded to react 16 but had the same issue.
Background:
We use Vite js to bundle the javascript which I don't think is related but definitely could be. The app runs in an alpine docker environment but I can reproduce it locally so I don't think its specific environment related. Rails routes are empty endpoints that serves an empty html page with a #root div for React to hydrate and route accordingly. We're not using the react-rails gem. In the output of the html page, the assets are all pointing at the correct js files and the code does exist in those files.
Code
The main runtime code for capybara tests. I've included the code for the small react snippet I tested with the capybara test and the output of the print page.html.
app/javascript/entrypoints/test.jsx
import React from "react"
import ReactDOM from "react-dom"
// If this is uncommented, this line runs correctly but is not
// replaced by the "Hello World" in the `render` method
// document.getElementById("root").innerText = "Foo bar"
// This never gets run or is run incorrectly
ReactDOM.render(
<div>Hello World</div>,
document.getElementById("root")
)
test.html.haml (yes, I know haml is awful)
!!!
%html{lang: :en}
%head
= vite_client_tag
= vite_react_refresh_tag
= vite_javascript_tag "test.jsx"
%body
#root
react_test_spec.rb
require "rails_helper"
RSpec.describe "Testing react", type: :feature, js: true do
describe "just checking", :with_csrf do
before { visit test_home_path }
subject { page }
it "renders react" do
print page.html
expect(page).to have_content "Hello World"
end
end
end
print page.html output
<html lang="en"><head>
<script src="/vite-test/assets/test.92ee76c9.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite-test/assets/jsx-dev-runtime.ddafb254.js" as="script" crossorigin="anonymous">
</head>
<body>
<div id="root"></div>
</body></html>
Config/setup code. Package versions, capybara/vite configuration, and etc.
package.json
// react related packages
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-router-dom": "6",
// vite related packages
"stimulus-vite-helpers": "^3.0.0",
"vite": "^2.9.1",
"vite-plugin-ruby": "^3.0.9",
"vite-plugin-stimulus-hmr": "^3.0.0",
"#vitejs/plugin-react": "^1.3.2",
// babel related packages
"#babel/core": "^7.0.0-0",
"#babel/preset-react": "^7.16.7",
"#babel/preset-typescript": "^7.17.12",
"#babel/eslint-parser": "^7.17.0",
"#babel/plugin-transform-runtime": "^7.18.2",
"#babel/preset-env": "^7.17.10",
"babel-jest": "^27.5.1",
"babel-plugin-macros": "^3.1.0",
capybara.rb
Capybara.register_driver :chrome_headless do |app|
options = ::Selenium::WebDriver::Chrome::Options.new
options.add_argument("--headless")
options.add_argument("--no-sandbox")
options.add_argument("--disable-dev-shm-usage")
options.add_argument("--window-size=1400,1400")
Capybara::Selenium::Driver.new(app, browser: :chrome, capabilities: [options])
end
Capybara.javascript_driver = :chrome_headless
vite.config.ts
export default defineConfig({
build: {
sourcemap: true,
},
plugins: [RubyPlugin(), react(), StimulusHMR()],
})
vite.json
{
"all": {
"sourceCodeDir": "app/javascript",
"watchAdditionalPaths": []
},
"development": {
"autoBuild": true,
"publicOutputDir": "vite-dev",
"port": 3036
},
"test": {
"autoBuild": true,
"publicOutputDir": "vite-test",
"port": 3037
}
}
some packages in Dockerfile.development. Also duplicated this issue locally using chromedriver
RUN apk add \
build-base \
chromium \
chromium-chromedriver \
Your JS assets are likely built differently in dev and test modes - and this sounds like you have a JS bug which is preventing the hydration. Add a pause to your test, run it in non-headless mode and look at the developer console for JS/network errors

Invalid hook creating Liferay React component

I'm trying to create custom react components based on Liferay's Clay components.
Using e.g. just a ClayButton works, but as soon as i try to use hooks (like React.useState), the browser console tells me:
Minified React error #321; visit https://reactjs.org/docs/error-decoder.html?invariant=321 for the full message
The full message tells me i could be using mismatching versions of react and react-dom. I'm not.
I also don't have 2 different versions of react, according to the test described there.
I created a minimal example module at https://github.com/ReFl3x0r/liferay-react-component-test which can be tested in a Liferay Gradle Workspace.
There's also an older thread in Liferay Forums discussing this error, but with no solution.
(https://liferay.dev/ask/questions/development/re-lr-7-3-react-portlet-invalid-hook-call)
What am i doing wrong?
EDIT:
Trying to point out the main code snippets.
First CustomButtonFail.es.js:
import React from 'react';
import ClayButton from '#clayui/button';
const CustomButton = () => {
const [name, setName] = React.useState('test');
return (
<ClayButton displayStyle='primary'>
TEST
</ClayButton>
);
}
export default CustomButton;
The package.json:
{
"dependencies": {
"#clayui/button": "^3.40.0",
"#clayui/css": "3.x",
"react": "^16.12.0",
"react-dom": "^16.12.0"
},
"devDependencies": {
"#liferay/npm-scripts": "47.0.0",
"react-test-renderer": "^16.12.0"
},
"name": "component-test",
"scripts": {
"build": "liferay-npm-scripts build"
},
"version": "1.0.0"
}
The view.jsp including the component (shortened):
<%#taglib uri="http://liferay.com/tld/react" prefix="react" %>
<div class="react-component-failing">
<react:component
module="js/CustomButtonFail.es"
/>
</div>
I finally got it working. Reducing package.json like this:
{
"devDependencies": {
"#liferay/npm-scripts": "47.0.0"
},
"name": "component-test",
"scripts": {
"build": "liferay-npm-scripts build"
},
"version": "1.0.0"
}
and adding a ".npmbundlerrc" in modules root with content:
{
"config": {
"imports": {
"frontend-taglib-clay": {
"#clayui/button": ">=3.40.0",
"#clayui/css": ">=3.x"
},
"#liferay/frontend-js-react-web": {
"react": ">=16.12.0"
}
}
}
}
did the trick.
Working example is at https://github.com/ReFl3x0r/liferay-react-component-test/tree/working

Why is “declare var angular” necessary when using Typescript with AngularJS?

I have a functional AngularJS 1.7 application where all my code is written in TypeScript. But something's always bothered me.
In my app.module.ts file, I have this bit of ugliness:
declare var angular;
in order for the
this.app = angular.module('app'...
to transpile and run.
Things I've tried:
1) Replacing declare var angular; with
import angular from "angular";
transpiles just fine, but then the browser complains
Uncaught (in promise) TypeError: angular_1.default.module is not a function
2) Replacing declare var angular; with
import * as angular from "angular";
also transpiles fine, but the browser gives a similar error:
Uncaught (in promise) TypeError: angular.module is not a function
3) Replacing declare var angular; with
import ng from "angular";
and then using ng.angular.module or ng.module doesn't transpile at all.
I'm sure I've tried some other things, but the only way I've ever been able to get things to work is with that declare var angular;
It all works fine, but that sure smells bad to me. Why do I have to do that? What am I doing wrong? Is there a better way to do it?
Details:
Visual Studio 2017/2019
TypeScript 3.3
SystemJS (not RequireJS)
AngularJS 1.7.8
#types/angular 1.6.54
package.json
"devDependencies": {
"#types/angular": "^1.6.54",
...
},
"dependencies": {
"angular": "~1.7.8",
...
}
tsconfig.json
{
"compileOnSave": true,
"compilerOptions": {
"baseUrl": ".",
"paths": {
"*": [ "node_modules/types/*", "node_modules/*" ],
},
"module": "system",
"target": "es6",
"sourceMap": true,
"lib": [ "es6", "dom" ],
"allowSyntheticDefaultImports": true,
"outFile": "./app/everything.js",
"moduleResolution": "node",
"types": [
"angular",
"jquery"
]
},
"include": ["app/**/*"],
"exclude": ["node_modules", "lib"],
"strict": true
}
app.module.ts
declare var angular;
...
export class App {
app: ng.IModule;
constructor() {
this.app = angular.module('app', [
...
]);
}
public run() {
this.app.run([
...
]);
...
}
index.html
...
<script src="lib/systemjs/system.js"></script>
<script src="lib/angular/angular.min.js" type="text/javascript"></script>
...
<script src="app/everything.js" type="text/javascript"></script>
<script>
System.config({
map: {
"angular": "lib/angular",
...
},
packages: {
"angular": { main: "angular.min.js", defaultExtension: "js" },
...
}
});
System.import("app.module")
.then(function (app) {
let a = new app.App();
a.run();
angular.element(function () {
angular.bootstrap(document, ['app']);
});
});
</script>
Your Typescript types, #types/angular, look for the angular variable to attach their types to.
Normally, you'd have imported angular like this: import angular from "angular" and the types would be applied to the angular variable you just created, and all would work just fine.
However, you are already loading Angular globally in your HTML <script>. In this case, you don't want to import angular from "angular" because then you will have loaded Angular more than once, and things might break.
By writing declare var angular you are simply telling Typescript to trust you that it exists, even though it hasn't seen it be imported into the scope of this file.
This means
Typescript won't complain about Angular being undefined.
#types/angular will attach to your variable angular.
When you get to runtime, assuming angular does really exist on window, then it will work.
This is a problem that will be required while you are moving to bundlers like Webpack or SystemJS. Once you remove the global Angular from index.html <script>, you can import into your TS files and delete the declare var.

WARNING: Tried to load angular more than once. - GRUNT

My pipeline looks like this:
// Client-side javascript files to inject in order
// (uses Grunt-style wildcard/glob/splat expressions)
var jsFilesToInject = [
// Dependencies like sails.io.js, jQuery, or Angular
'js/dependencies/sails.io.js',
'js/bower_components/angular/*.js',
'js/bower_components/**/*.js',
// All of the rest of your client-side js files
// will be injected here in no particular order.
'js/modules/app.js',
'js/modules/auth/services/accessLevels.js',
'js/**/*.js'
];
and my bower.json:
{
"name": "myapp",
"version": "0.0.1",
"dependencies": {
"angular": "1.3.7",
"angular-cookies": "1.3.7",
"angular-ui-router": "0.2.15"
},
"resolutions": {
"angular": "1.3.7"
}
}
I think this issue is because it's injecting angular.js and angular.min.js, any way to solve this with grunt?

Gulp throws an error when bundling angular and angular-hammer with browserify

Gulp throws an unclear error when I am trying to bundle angular and angular-hammer (the Ryan Mullins version) with browserify.
For a stripped down version of the app, the package.json file is:
{
"name": "hammer-test",
"version": "0.0.0",
"description": "",
"main": "app/main.js",
"devDependencies": {
"browserify": "^10.2.1",
"browserify-shim": "^3.8.7",
"gulp": "^3.8.11",
"vinyl-source-stream": "^1.1.0"
},
"dependencies": {
"angular": "^1.3.15",
"angular-hammer": "^2.1.10",
"hammerjs": "^2.0.4"
}
}
(I included browserify-shim, because an error was thrown asking for this missing dependency for angular-hammer.)
The gulpfile.js contains a bundle task to run browserify:
'use strict';
var browserify = require('browserify');
var gulp = require('gulp');
var source = require('vinyl-source-stream');
// Bundle (browserify).
gulp.task('bundle', function() {
return browserify('./app/js/main.js')
.bundle()
.pipe(source('bundle.js'))
.pipe(gulp.dest('./dist/js'));
});
gulp.task('default', ['bundle']);
And finally, the main javascript file app/js/main.js contains:
(function() {
'use strict';
// Require stuff.
var angular = require('angular');
var Hammer = require('hammerjs');
require('angular-hammer');
// Initialize angular application.
var app = angular.module('myApp', ['hmTouchEvents']);
}());
The directory structure of the app now looks like this:
- app
- js
-main.js
- node_modules
- angular
- angular-hammer
- browserify
- browserify-shim
- gulp
- hammerjs
- vinyl-source-stream
- gulpfile.js
- package.json
When I now try to run the bundle task using the command:
gulp
Then the following error is thrown:
events.js:72
throw er; // Unhandled 'error' event
^
Error: ENOENT, open '/home/brennerd/Develop/hammer-test/node_modules/angular-hammer/node_modules/angular/angular.js'
The error is not very descriptive, but some paths seem to be incorrectly concatenated. Did I make a browserify mistake somewhere? My browserify experience is limited, so that could very well be the case. Or is it not possible to bundle angular-hammer with browserify?
Thanks!
After some searching, learning, and playing around I found the issue. It was indeed a browserify mistake.
Thanks to the documentation of the angular-hammer-propagating fork I figured out that the package.json needed some extension. As far as I understand to 1) let browserify know that it should run browserify-shim and 2) to let browserify-shim know where to find the angular-hammer dependencies. The package.json now looks like this:
{
"name": "hammer-test",
"version": "0.0.0",
"description": "",
"main": "app/main.js",
"devDependencies": {
"browserify": "^10.2.1",
"browserify-shim": "^3.8.7",
"gulp": "^3.8.11",
"vinyl-source-stream": "^1.1.0"
},
"dependencies": {
"angular": "^1.3.15",
"angular-hammer": "^2.1.10",
"hammerjs": "^2.0.4"
},
"browserify": {
"transform": [
"browserify-shim"
]
},
"browser": {
"angular-hammer": "./node_modules/angular-hammer/angular.hammer.js"
},
"browserify-shim": {
"angular-hammer": {
"exports": "angular.module('hmTouchEvents').name",
"depends": [
"./node_modules/hammerjs/hammer.js:Hammer"
]
}
}
}
For testing I added a HammerTestCtrl with the handleTap function to the main.js file:
(function() {
'use strict';
// Require both angular and angular-hammer.
var angular = require('angular');
require('angular-hammer');
// Initialize angular application.
var app = angular.module('myApp', ['hmTouchEvents']);
// Add controller to test hammer.
app.controller('HammerTestCtrl', ['$scope', function($scope) {
$scope.handleTap = function() {
console.log('Tap detected.');
}
}]);
}());
I created a basic index.html with a square div that calls the handleTap function when it is tapped:
<!DOCTYPE html>
<html lang="en" ng-app="myApp">
<head>
<meta charset="utf-8">
<title>Test hammer-angular</title>
<script src="js/bundle.js"></script>
</head>
<body ng-controller="HammerTestCtrl">
<div hm-tap="handleTap" style="width: 300px; height: 300px; background-color: #eeeeec;"></div>
</body>
</html>
And in the gulpfile.js I added a task to move this index.html from the app to the dist directory:
'use strict';
var browserify = require('browserify');
var gulp = require('gulp');
var source = require('vinyl-source-stream');
// Bundle (browserify).
gulp.task('bundle', function() {
return browserify('./app/js/main.js')
.bundle()
.pipe(source('bundle.js'))
.pipe(gulp.dest('./dist/js'));
});
// HTML.
gulp.task('html', function() {
gulp.src('app/index.html')
.pipe(gulp.dest('dist/'));
});
gulp.task('default', ['bundle', 'html']);
After an npm install and this time successful gulp, the directory structure looks like this:
- app
- index.html
- js
-main.js
- dist
- index.html
- js
- bundle.js
- node_modules
- angular
- angular-hammer
- browserify
- browserify-shim
- gulp
- hammerjs
- vinyl-source-stream
- gulpfile.js
- package.json
After browsing to the dist/index.html file it correctly outputs Tap detected. to the console every time the light-gray square is tapped. The code for this angular-hammer boilerplate web application can be found here.

Resources