Javascript for everyone
Розповім про всі радості сучасного JavaScript, починаючи від базового синтаксису і закінчуючи (обережно, жаргон!) модулями, бандлерами і транспайлерами. Перед тим як розпочати — декілька дісклеймерів про зміст лекції:
- Деякі терміни та технології розглядаються поверхнево;
- "— А клікати на всі лінки на документацію і читати всі-всі статті?" Ну-у-у-у-у, up to you;
- Все мало би бути цікаво та по максимуму інформативно, але тут вже як вийде... Have fun! 🙃
І ще один heads up — вам знадобиться улюблена IDE, Node.js, і стартер проекту (складатиму його по ходу лекції). Готові? Побігли!.
Basic JavaScript
Difficulty: Nothing special 🤷♂️. Objectives: Get a general idea about JavaScript.Ready to try JavaScript?
To start with, here are the main points you should remember about JS:
Brendan EichEcmaScriptscript languagedynamic languageweakly typedfirst-class functionsobject-orientedprototype-basedsingle-threadedasynchronous
The syntax of comments is the same as in C++ and in many other languages. Comments behave like white space and are discarded during script execution.
const style = { display: 'flex', flexWrap: 'wrap' // dominantBaseline: central; not supported by IE 9 :( }
/* * I'm a large and bulky * comment */ function foo() { console.log('bar'); } /** * returns the name of the lecture * @param {string} course 'js' | 'net' * @returns {string | undefined} */ function getLectureName(course) { switch (course) { case "js": return "Introduction to JavaScript"; case "net": return "C# and .NET framework"; } }
Declaring block:
A block statement (or compound statement in other languages) is used to group zero or more statements. The block is delimited by a pair of braces:
{ // I'm a block and I have my own scope here }
Declaring variables:
var
,let
andconst
There are three kinds of declarations in JavaScript:
- Declaration will be hoisted to the global scope or a function block scope. Variable defined inside of the inner scope can override another variable from the outer scope. That's why it mustn't be used in the modern JS code!
var message; message = 'Hello World'; var name = 'John', age = 24; var count = 10; if (count > 5) { var count = 5; // Not declares a new variable, but using existing hoisted count } // count is equal to 5 😰
- Declaration won't be hoisted but will create a new locally scoped variable.
let message; message = 'Hello World'; let name = 'John', age = 24; let count = 10; if (count > 5) { let count = 5; // Declares a new local variable, which doesn't interfere with the outer scope // count is equal to 5 here 👌 } // count is still equal to 10 👌
- Variable cannot be reassigned, but objects defined with
const
can be changedconst PRIMARY_COLOR = '#ccc'; PRIMARY_COLOR = '#fff'; // ❌ const user = { name: 'John', age: 20 }; user.name = 'Oleksandr'; // 👌 user.newProp = 'newProp'; // 👌 user = 'newValue'; // ❌
Data Types
According to the ECMAScript, there are seven primitive types and everything else is an
Object
type. Let’s run over those one by one:The boolean type has two values:
true
andfalse
.let isValid = true; isValid = false;
An integer or floating point number.
const age = 23; const coef = 12.345;
The number type has three symbolic values: `+Infinity`, `-Infinity`, and `NaN` (not-a-number).const hex = 0x00111; // hexadecimal, base 16 starts with 0x const octal = 0o01; // octal, base 8 starts with 0o const binary = 0b0011; // binary, base 2 starts with 0b
const result = 2 / "text"; // NaN isNaN(result); // true
Represents who represent whole numbers larger than
253 - 1
which is larger than maximum value ofNumber
type.const bigInt = 9007199256474096991n; const evenLargerInt = BigInt("99007199256474096991");
A sequence of characters that represent a text value. JavaScript strings are immutable.
let str = "Hello world"; str = 'new string'; let age = 23; let newStrOld = 'age - ' + age; let newStr = `age - ${age}`; console.log(`first line second line`);
str.length // 10 str.charAt(2) // w str.toLowerCase() // "new string" str.toUpperCase() // "NEW STRING" str.indexOf('str') // 4 newStr.substring(11, 17) // second newStr.substr(11, 6) // second
A Symbol is a unique and immutable primitive value and may be used as the key of an object property. So we can “covertly” hide something into objects that we need, but others should not see, using symbolic properties.
const isAdmin = Symbol('isAdmin'); const user = { name: 'John', isAdmin: false, [isAdmin]: true } console.log(user.isAdmin); // false console.log(user[isAdmin]); // true
undefined
is a property of the global object. The initial value is the primitive valueundefined
let x; // undefined const noReturnValue = foo(); // undefined function foo() { return; }
null
is a keyword denoting a special empty (null) value.let x = null;
JavaScript variables can be converted to a new variable and another data type either by the use of a JavaScript function or automatically by JavaScript itself:
Converting to string:
String(null) // "null" false.toString(); // "false" 12 + 'px' // "12px"
Converting to number:
Number(true) // 1 +true // 1 parseInt('3.14', 10) // 3 parseFloat('3.14') // 3.14 12 - '4' // 8 12 - 'text' // NaN 12 + +'4' // 16
Converting to boolean:
0, '', null, undefined, NaN -> false Boolean(null); // false !!null; // false if (true || 12) { // true if (true && 0) { // false if (12) { // will be executed }
Objects, which are created in the global scope and can be accessed wherever in the code. They can be created by the user script or provided by the host environment (browser, node). Let's look through the common ones:
- Represents complex Javascript data type. It is used to store various keyed collections and more complex nested entities.
const obj = { foo: 'bar', nested: { bar: 'foo' } } Object.keys(obj); // ['foo', 'nested'] Object.values(obj); // ['bar', { bar: 'foo' }] Object.entries(obj); // [ ['foo', 'bar'], ['nested', { bar: 'foo' }] ]
- List-like object, which includes methods to perform traversal and mutation operations. Each element can be accessed only by its index. Neither the length of a JavaScript array nor the types of its elements are fixed.
let names; names = new Array('Anna', 'Alex', 'Alicia'); names = ['Anna', 'Alex', 'Alicia']; names[0]; // 'Anna'. Indices range [0..n-1] names.includes('Alex') // true arr.push(element); // add to the end arr.unshift(element); // add to the front arr.splice(index, 1, element); // insert element on index arr.splice(index, 1); // remove element on index arr.pop(); // remove from the end arr.shift(); // remove from the front
- Object, which holds key-value pairs and remembers the original insertion order of the keys. Any value (both objects and primitive values) may be used as either a key or a value.
const binarians = new Map(); binarians.set('JsExpert', 'Alicia'); binarians.set('PhpExpert', 'Volodymyr'); binarians.set('JsExpert', 'Alex'); binarians.get('JsExpert'); // 'Alex' binarians.delete('PhpExpert');
- Object allows storing unique values of any type, whether primitive values or object references. Plays a role as an array of unique values.
const uniqueNames = new Set(); uniqueNames.add("Alex"); uniqueNames.add("Alicia"); uniqueNames.add("Alex"); // ['Alex', 'Alicia'] uniqueNames.has('Alex'); // true uniqueNames.delete('Alex');
- Object which holds pattern used to match character combinations in strings.
const regexp = /ab+c/; // compiling literal, preferred const regexp = new RegExp('ab+c' + variable); regexp.test('abc'); // true
- Object represents a single moment in time in a platform-independent format. Internally contains a Number that represents milliseconds since 1 January 1970 UTC.
const dateCurrent = new Date(); const dateMarch = new Date('March 13, 2000, 08:04:20'); dateMarch.getFullYear(); // 2000 dateMarch.getHours(); // 4
- Object that has properties and methods for mathematical constants and functions. Works with Number type, but doesn't support BigInt type
Math.PI; // 3.141592653589793 Math.E; // 2.718281828459045 ... Math.sin(1); // 0.8414709848078965 Math.cos(1); // 0.5403023058681398 Math.abs(-3); // 3 ... Math.max(1, 2, 3, 10); // 10 Math.min(1, 2, 3, 10); // 1 Math.random(); // [0..1) ...
- Object contains methods for parsing JSON and converting stringified values to JSON.
const json = { value: "123" } const stringifiedJson = JSON.stringify(json); // "{"value":"123"}" const parsedJson = JSON.parse(stringifiedJson); // { value: "123" }
Function is a "subprogram" that can be called by code external (or internal in the case of the recursion) to the function.
A function is composed of a sequence of statements called the function body. Values can be passed to a function, and the function will return a value. In JavaScript, functions are first-class objects, because they can have properties and methods just like any other object.
There are three ways to declare function in Javascript:function foo() {}
foo(); // Can be accessed before declaration function foo() { console.log(`I'm a hoisted function ${bar()}`); // "I'm a hoisted function with a nested function" function bar() { return "with a nested function"; } }
const obj = { prop: 1, func: contextLog } obj.func(); function contextLog() { console.log(this); // { prop: 1, func: f } }
const foo = function () {}
const foo = function() { const bar = function() { return "with a nested function"; } console.log(`I'm not a hoisted function ${bar()}`); // "I'm not a hoisted function with a nested function" } foo(); // Must be accessed only after declaration
const obj = { prop: 1, func: function() { console.log(this); // { prop: 1, func: f } } } obj.func();
const foo = () => {}
const foo = () => { const bar = () => { return "with a nested arrow function"; } console.log(`I'm not a hoisted arrow function ${bar()}`); // "I'm not a hoisted arrow function with a nested arrow function" } foo(); // Must be accessed only after declaration
const obj = { prop: 1, func: () => { console.log(this); // { Window } }, functionNestedExpressions: function() { const nestedExpression = function() { console.log(this); // { Window } } console.log(this); // { prop: 1, func: f, functionNestedExpressions: f, functionNestedArrow: f } nestedExpression(); }, functionNestedArrow: function() { const nestedArrow = () => { console.log(this); // { prop: 1, func: f, functionNestedExpressions: f, functionNestedArrow: f } } console.log(this); // { prop: 1, func: f, functionNestedExpressions: f, functionNestedArrow: f } nestedArrow(); } } obj.func(); obj.functionNestedExpressions(); obj.functionNestedArrow();
setTimeout(() => console.log("I have waited a second to run"), 1000);
Loops offer a quick and easy way to do something repeatedly. The statements for loops provided in JavaScript are:
for (let step = 0; step < stepCount; step++) { const value = step * intervalComputed; if (value === 0) { labelsFromRange.push('0'); continue; } const labelText = formatNumber(value, decimalSeparator); const labelWidth = getTextWidth(labelText); const isLastStep = step === stepCountWithMargin; if (isLastStep && remainingAxisLength < labelWidth) { break; } labelsFromRange.push(labelText); }
let level = currentLevel; while(level >= 0) { // do something level--; }
do { // do something level--; } while (lavel >= 0);
const users = ['Mike', 'Alex']; users.forEach((value, index, array) => console.log(value)); // 'Mike', 'Alex' users.forEach((value, index) => { if (index > 0) return; // Skips the iteration console.log(value); // 'Mike' }); // LIFEHACK // Traverse not all the elements of the huge arrays users.some((value, index) => { if (value === 'Skip me!') return false; // Skips the iteration if (index === 1) return true; console.log(value); // 'Mike' });
const users = ['Mike', 'Alex']; for (let value of users) { console.log(value); // Mike, Alex } const user = { firstName: 'Mike', lastName: 'Din' }; for (let value of user) { console.log(value); // Mike, Din } Object.values(user).forEach(value => console.log(value)); // Mike, Din
const obj = { prop1: "I'm a property 1", prop2: "Property indeed!" } for (const prop in obj) { // OWN + INHERITED console.log(prop); // prop1, prop2 } Object.keys(obj) // ONLY OWN PROPERTIES .forEach(value => console.log(value)); // prop1, prop2
Building a webpage
Difficulty: Let's spice it up 🔥. Objectives: Gain practical knowledge.const API_URL = 'https://api.github.com/repos/oleksandr-danylchenko/street-fighter/contents/resources/api/fighters.json'; const SECURITY_HEADERS = { headers: { authorization: "token %your_token%" } }; fetch(API_URL, SECURITY_HEADERS);
const responsePromise = fetch(API_URL, SECURITY_HEADERS); console.log(responsePromise);
responsePromise.then(response => { console.log(response) });
responsePromise.then(response => response.json());
fetch(API_URL, SECURITY_HEADERS) .then(response => response.json()) .then(file => { const fighters = JSON.parse(atob(file.content)); console.log(fighters); });
const names = fighters.map(it => it.name); const namesStr = names.join('\n');
const rootElement = document.getElementById('root'); rootElement.innerText = 'Loading...'; fetch(API_URL, SECURITY_HEADERS) .then(response => response.json()) .then(file => { const fighters = JSON.parse(atob(file.content)); const names = fighters.map(it => it.name); const namesStr = names.join('\n'); rootElement.innerText = namesStr; });
- Learn more about Promises:
fetch(API_URL, SECURITY_HEADERS) .then( response => response.json(), error => console.warn(error) )
fetch(API_URL, SECURITY_HEADERS) .then() .then() .catch(error => { console.warn(error); rootElement.innerText = 'Failed to load data'; });
fetch(API_URL, SECURITY_HEADERS) .then(response => { if (!response.ok) { throw new Error('Failed load data'); } return response.json(); })
if (!response.ok) { return Promise.reject(Error('Failed load data')); }
<div id="loading-overlay"> <img src="resources/logo.png"/> </div>
const loadingElement = document.getElementById('loading-overlay'); loadingElement.remove();
fetch(API_URL, SECURITY_HEADERS) .then() .then() .catch() .finally(() => { loadingElement.remove(); });
const greetingPromise = Promise.resolve({ value: 'Hello World' }); greetingPromise.then(res=> { console.log(res.value + '!') });
const promise = new Promise((resolve, reject) => { // some asynchronous code let isValid = true if (isValid) { resolve("Success"); } else { reject(Error("Error")); } });
const createTimeoutPromise = (message, ms) => new Promise(resolve => { setTimeout(() => resolve(message), ms); }); const one = createTimeoutPromise('first resolved', 2000); const two = createTimeoutPromise('second resolved', 3000); const promiseRace = Promise.race([one, two]); const promiseAll = Promise.all([one, two]); promiseRace.then(res => console.log(res)); // first resolved promiseAll.then(res => console.log(res)); // ['first resolved', 'second resolved']
const BASE_API_URL = 'https://api.github.com/'; const SECURITY_HEADERS = { headers: { authorization: "token %your_token%" } }; const rootElement = document.getElementById('root'); const loadingElement = document.getElementById('loading-overlay'); function startApp() { const endpoint = 'repos/oleksandr-danylchenko/street-fighter/contents/resources/api/fighters.json'; const fightersPromise = callApi(endpoint, 'GET'); fightersPromise.then(fighters => rootElement.innerText = getFightersNames(fighters) ); } function callApi(endpoint, method) { const url = BASE_API_URL + endpoint; const options = { method, ...SECURITY_HEADERS }; return fetch(url, options) .then(response => response.ok ? response.json() : Promise.reject(Error('Failed to load'))) .then(file => JSON.parse(atob(file.content))) .catch(error => { console.warn(error); rootElement.innerText = 'Failed to load data'; }) .finally(() => { loadingElement.remove(); }); } function getFightersNames(fighters) { return fighters.map(it => it.name).join('\n'); } startApp();
function callApi(endpoind, method = 'GET') { }
async function startApp() { const endpoint = 'repos/oleksandr-danylchenko/street-fighter/contents/resources/api/fighters.json'; const fighters = await callApi(endpoint); rootElement.innerText = getFightersNames(fighters); }
const fighters = await callApi(endpoint); const fightersArr = await callApi(endpoint); const [fighters, fightersArr] = await Promise.all([ callApi(endpoint), callApi(endpoint) ]);
function* generateSequence() { const result = yield 1; console.log(result); // 'args' yield 2; yield 3; } let generator = generateSequence(); let one = generator.next(); // {value: 1, done: false} let two = generator.next('args'); // {value: 2, done: false} let three = generator.next(); // {value: 3, done: true}
async function startApp() { try { loadingElement.style.visibility = 'visible'; const endpoint = 'repos/oleksandr-danylchenko/street-fighter/contents/resources/api/fighters.json'; const fighters = await callApi(endpoint); rootElement.innerText = getFightersNames(fighters); } catch (error) { console.warn(error); rootElement.innerText = 'Failed to load data'; } finally { loadingElement.style.visibility = 'hidden'; } } function callApi(endpoint, method = 'GET') { const url = BASE_API_URL + endpoint; const options = { method, ...SECURITY_HEADERS }; return fetch(url, options) .then(response => response.ok ? response.json() : Promise.reject(Error('Failed to load')) ) .then(file => JSON.parse(atob(file.content))) .catch(error => { throw error }); }
- Create more DOM elements and add event listeners
function createElement({ tagName, className = '', attributes = {} }) { const element = document.createElement(tagName); element.classList.add(className); Object .keys(attributes) .forEach(key => element.setAttribute(key, attributes[key])); return element; }
function createName(name) { const nameElement = createElement({ tagName: 'span', className: 'name' }); nameElement.innerText = name; return nameElement; } function createImage(source) { const attributes = { src: source }; const imgElement = createElement({ tagName: 'img', className: 'fighter-image', attributes }); return imgElement; }
function createFighter(fighter) { const { name, source } = fighter; const nameElement = createName(name); const imageElement = createImage(source); const element = createElement({ tagName: 'div', className: 'fighter' }); element.append(imageElement, nameElement); return element; }
Destructuring assignment — expression that unpacks values from arrays, or properties from objects, into distinct variables.
const obj = { a: true, b: true, c: true }; // ❌ const a = obj.a; const secondProperty = obj.b; // 👌 const { a, b: secondProperty } = obj; const words = ['a', 'b']; const [firstElement] = words; // ['a']
function createFighters(fighters) { const fighterElements = fighters.map(fighter => createFighter(fighter)); const element = createElement({ tagName: 'div', className: 'fighters' }); element.append(...fighterElements); return element; }
Using the spread operator and rest parameter:
const words = ['a', 'b']; const extendedWords = [...words, 'c']; // ['a', 'b', 'c'] const obj = { a: true, b: true }; const extendedObj = { ...obj, c: true }; // { a: true, b: true, c: true }
const words = ['a', 'b']; const [firstElement, ...restElements] = words; // ['a', ['b']] const obj = { a: true, b: true }; const { a, ...restProps } = obj; // { a: true, restProps: { b: true } }
const fightersElement = createFighters(fighters); rootElement.appendChild(fightersElement);
element.addEventListener('click', (event) => handleFighterClick(event, 'wrapper'), false) imageElement.addEventListener('click', (event) => handleFighterClick(event, 'image'), false) function handleFighterClick(event, el) { console.log(el); }
const fightersDetailsMap = new Map(); element.addEventListener('click', (event) => handleFighterClick(event, fighter), false) function handleFighterClick(event, fighter) { const { _id } = fighter; if(!fightersDetailsMap.has(_id)) { // send request here fightersDetailsMap.set(_id, fighter); } console.log(fightersDetailsMap.get(_id)); }
- Add classes (use inheritance and static properties)
class FighterService { #endpoint = 'repos/oleksandr-danylchenko/street-fighter/contents/resources/api/fighters.json' async getFighters() { try { const apiResult = await callApi(this.#endpoint, 'GET'); return JSON.parse(atob(apiResult.content)); } catch (error) { throw error; } } } const fighterService = new FighterService();
class View { element; createElement({ tagName, className = '', attributes = {} }) { const element = document.createElement(tagName); element.classList.add(className); Object.keys(attributes).forEach(key => element.setAttribute(key, attributes[key])); return element; } }
get element() { return this.element; } set element(value) { this.element = value; }
class FighterView extends View { constructor(fighter, handleClick) { super(); this.createFighter(fighter, handleClick); } createFighter(fighter, handleClick) { const { name, source } = fighter; const nameElement = this.createName(name); const imageElement = this.createImage(source); this.element = this.createElement({ tagName: 'div', className: 'fighter' }); this.element.append(imageElement, nameElement); this.element.addEventListener('click', event => handleClick(event, fighter), false); } createName(name) { const nameElement = this.createElement({ tagName: 'span', className: 'name' }); nameElement.innerText = name; return nameElement; } createImage(source) { const attributes = { src: source }; return this.createElement({ tagName: 'img', className: 'fighter-image', attributes }); } } class FightersView extends View { fightersDetailsMap = new Map(); constructor(fighters) { super(); this.createFighters(fighters); } createFighters(fighters) { const fighterElements = fighters.map(fighter => { const fighterView = new FighterView(fighter, this.handleFighterClick); return fighterView.element; }); this.element = this.createElement({ tagName: 'div', className: 'fighters' }); this.element.append(...fighterElements); } handleFighterClick(event, fighter) { this.fightersDetailsMap.set(fighter._id, fighter); console.log('clicked') // get from map or load info and add to fightersMap // show modal with fighter info // allow to edit health and power in this modal } }
class Lumberjack { cut() { console.log("Cutting tree"); } } class SmartLumberjack extends Lumberjack { cut() { console.log("Measuring sizes of the tree"); super.cut(); } } const smartLumberjack = new SmartLumberjack(); smartLumberjack.cut(); /* * Measuring sizes of the tree * Cutting tree */
class App { static rootElement = document.getElementById('root'); static loadingElement = document.getElementById('loading-overlay'); static async startApp() { try { App.loadingElement.style.visibility = 'visible'; const fighters = await fighterService.getFighters(); const fightersView = new FightersView(fighters); App.rootElement.appendChild(fightersView.element); } catch (error) { console.warn(error); App.rootElement.innerText = 'Failed to load data'; } finally { App.loadingElement.style.visibility = 'hidden'; } } } App.startApp();
// .bind() const obj = { a: 1 }; const bindedFunction = foo.bind(obj); bindedFunction(); // { a: 1 } function foo() { console.log(this); }
// .call() const obj = { a: 1 }; foo.call(obj); // { a: 1 } function foo() { console.log(this); }
// .apply() const obj = { a: 1 }; const args = ['firstArg', 'secondArg']; foo.apply(obj, args); // { a: 1 }, 'firstArg', 'secondArg' foo.call(obj, ...args); // alternative function foo(arg1, arg2) { console.log(this, arg1, arg2); }
class FightersView extends View { constructor(fighters) { super(); this.handleClick = this.handleFighterClick.bind(this); this.createFighters(fighters); } createFighters(fighters) { const fighterElements = fighters.map(fighter => { // 1. Class function with context const fighterView = new FighterView(fighter, this.handleClick); // 2. Inline context binding const fighterView = new FighterView(fighter, this.handleFighterClick.bind(this)); // 3. Arrow function const fighterView = new FighterView(fighter, (event, fighters) => this.handleFighterClick(event, fighters)); return fighterView.element; }); // ... } }
- 💥Boom, done! There is half already!
Modules, transpiling, bundling, etc.
Difficulty: And now we gonna roll 🤸. Objectives: Get the most out of the JS.Hold on, How JavaScript Classes Work Under the Hood again?
const person = { walk: true }; const student = { studying: 'Javascript' }; student.__proto__ = person; // ❌ - controversial and discouraged Object.setPrototypeOf(student, person); // 👌 - recommended alternative console.log(student.walk) // true
function Animal(name, sound) { this.name = name; this.sound = sound; }
Animal.prototype.makeSound = function() { console.log(`${this.name} ${this.sound}`); };
const cat = new Animal("Sam", "meowing"); console.log(cat.sound); cat.makeSound();
function Bird(name, sound) { Animal.apply(this, arguments); this._home = "tree"; // Private property naming convention } Bird.prototype = Object.create(Animal.prototype); Bird.prototype.constructor = Bird; Bird.prototype.fly = function() { Animal.prototype.makeSound.call(this); console.log(`Bird ${this.name} fly`); }; const bird = new Bird("Kiwi", "singing"); bird.fly();
Let's learn more about the transpilers — tools that read source code written in one programming language, and produce the equivalent code in another language. The most popular one, Babel, is mainly used to convert ECMAScript 2015+ code into a backwards compatible version of JavaScript in current and older browsers or environments. Let's try it out:
- Install node.js, open Terminal in Visual Studio Code (Terminal → New Terminal), and run
npm init
. - Now install babel with
npm i -D @babel/core @babel/cli
- Add
"scripts"
to the package.json fileand run"scripts": { "build": "babel index.js -d dist" }
npm run build
in the terminal to see if it works - Set up transpiling of ES2015+ code —
npm install @babel/preset-env --save-dev
, create a .babelrc in the root of your project, with the following content:{ "presets": [ [ "@babel/preset-env", { "targets": { "chrome": "67", "firefox": "60" } } ] ] }
- Let's watch for changes by running
babel
with-w
flag"scripts": { "build": "babel src -d dist -w" }
- Install node.js, open Terminal in Visual Studio Code (Terminal → New Terminal), and run
- What’s great about ES6 modules?
export const fighterService = new FighterService(); export { callApi as apiHelper }; export default View; export * from './apiHelper'; export { callApi } from './apiHelper'; export { default } from './View';
import App from './src/javascript/app'; import { fighterService } from './services/fightersService'; import { callApi as apiHelper } from '../helpers/apiHelper'; import * as helper from '../helpers/apiHelper'; helper.callApi();
- Alright, let's npminstall everything set up Webpack — a module bundler for JavaScript applications:
- Install Webpack by running
npm install webpack webpack-cli --save-dev
, then create a file named webpack.config.js in the root of your project, with the following content:const path = require('path'); module.exports = { entry: './index.js', output: { path: path.resolve(__dirname, 'dist'), filename: 'bundle.js' }, mode: 'development' }
- Set
build
command in package.json'sscripts
key to"build": "webpack"
Set up loaders:
npm install babel-loader --save-dev
and edit webpack.config.js to includemodule
key:Rename .babelrc to babel.config.js to consistent with webpack config type and change it's content to:module: { rules: [ { test: /\.m?js$/, exclude: /(node_modules|bower_components)/, use: [ { loader: "babel-loader", options: { configFile: "./babel.config.js", cacheDirectory: true } } ] } ] }
const presets = [ [ "@babel/preset-env", { targets: { firefox: "60", chrome: "67" } } ] ]; module.exports = { presets };
- As usual, just run
npm install css-loader style-loader --save-dev
and edit webpack.config.js to include the new rules:module: { rules: [ { test: /\.css$/, use: ["style-loader", "css-loader"] }, // other rules ] }
Set up plugins:
npm install html-webpack-plugin --save-dev
and edit webpack.config.js to includeplugins
key:// other settings plugins: [ new HtmlWebpackPlugin({ template: "index.html" }) ],
npm install html-loader --save-dev
and edit webpack.config.js to include the new rule:// other settings module: { rules: [ // other rules { test: /\.html$/, use: [ { loader: "html-loader" } ] } ] },
- Add Webpack DevServer —
npm install webpack-dev-server --save-dev
and modify webpack.config.js to includedevServer
andpublicPath
keys:output: { // other output settings publicPath: '/' }, // other Webpack settings devServer: { port: 9000 }
Add
"dev": "webpack-dev-server"
to package.json'sscripts
key - Maybe add
devtool
option to the webpack.config.js for much better debugging experience —devtool: "source-map"
- Install Webpack by running
- 🥁Badum tss! Відрефакторено!