build process
diff --git a/package.json b/package.json
index a3c0d50..f6debd1 100644
--- a/package.json
+++ b/package.json
@@ -8,7 +8,7 @@
"url": "https://github.com/airbnb/lottie-web.git"
},
"scripts": {
- "build": "npx eslint ./player/js/**/* && node tasks/build.js",
+ "build": "npx eslint ./player/js/**/* && rollup -c && node tasks/build_worker",
"build_test": "node tasks/build.js",
"lint": "npx eslint ./player/js/**/*",
"lint:fix": "npx eslint ./player/js/**/* --fix"
diff --git a/player/js/worker_wrapper.js b/player/js/worker_wrapper.js
new file mode 100644
index 0000000..598289b
--- /dev/null
+++ b/player/js/worker_wrapper.js
@@ -0,0 +1,629 @@
+function workerContent() {
+ var localIdCounter = 0;
+ var animations = {};
+
+ var styleProperties = ['width', 'height', 'display', 'transform', 'opacity', 'contentVisibility'];
+ function createElement(namespace, type) {
+ var style = {
+ serialize: function () {
+ var obj = {};
+ for (var i = 0; i < styleProperties.length; i += 1) {
+ var propertyKey = styleProperties[i];
+ var keyName = '_' + propertyKey;
+ if (keyName in this) {
+ obj[propertyKey] = this[keyName];
+ }
+ }
+ return obj;
+ },
+ };
+ styleProperties.forEach(function (propertyKey) {
+ Object.defineProperty(style, propertyKey, {
+ set: function (value) {
+ if (!element._isDirty) {
+ element._isDirty = true;
+ }
+ element._changedStyles.push(propertyKey);
+ var keyName = '_' + propertyKey;
+ this[keyName] = value;
+ },
+ get: function () {
+ var keyName = '_' + propertyKey;
+ return this[keyName];
+ },
+ });
+ });
+ localIdCounter += 1;
+ var element = {
+ _state: 'init',
+ _isDirty: false,
+ _changedStyles: [],
+ _changedAttributes: [],
+ _changedElements: [],
+ type: type,
+ namespace: namespace,
+ children: [],
+ attributes: {
+ id: 'l_d_' + localIdCounter,
+ },
+ style: style,
+ appendChild: function (child) {
+ child.parentNode = this;
+ this.children.push(child);
+ this._isDirty = true;
+ this._changedElements.push([child, this.attributes.id]);
+ },
+ insertBefore: function (newElement, nextElement) {
+ var children = this.children;
+ for (var i = 0; i < children.length; i += 1) {
+ if (children[i] === nextElement) {
+ children.splice(i, 0, newElement);
+ this._isDirty = true;
+ this._changedElements.push([newElement, this.attributes.id, nextElement.attributes.id]);
+ return;
+ }
+ }
+ children.push(nextElement);
+ },
+ setAttribute: function (attribute, value) {
+ this.attributes[attribute] = value;
+ if (!element._isDirty) {
+ element._isDirty = true;
+ }
+ element._changedAttributes.push(attribute);
+ },
+ serialize: function () {
+ return {
+ type: this.type,
+ namespace: this.namespace,
+ style: this.style.serialize(),
+ attributes: this.attributes,
+ children: this.children.map(function (child) { return child.serialize(); }),
+ };
+ },
+ getContext: function () { return { fillRect: function () {} }; },
+ addEventListener: function (_, callback) {
+ setTimeout(callback, 1);
+ },
+ setAttributeNS: function (_, attribute, value) {
+ this.attributes[attribute] = value;
+ if (!element._isDirty) {
+ element._isDirty = true;
+ }
+ element._changedAttributes.push(attribute);
+ },
+ };
+ element.style = style;
+ return element;
+ }
+
+ var window = self; // eslint-disable-line no-redeclare, no-unused-vars
+
+ var document = { // eslint-disable-line no-redeclare
+ createElementNS: function (namespace, type) {
+ return createElement(namespace, type);
+ },
+ createElement: function (type) {
+ return createElement('', type);
+ },
+ getElementsByTagName: function () {
+ return [];
+ },
+ };
+ /* eslint-enable */
+ var lottieInternal = (function () {
+ 'use strict';
+
+ /* <%= contents %> */
+
+ function addElementToList(element, list) {
+ list.push(element);
+ element._isDirty = false;
+ element._changedStyles.length = 0;
+ element._changedAttributes.length = 0;
+ element._changedElements.length = 0;
+ element.children.forEach(function (child) {
+ addElementToList(child, list);
+ });
+ }
+
+ function addChangedAttributes(element) {
+ var changedAttributes = element._changedAttributes;
+ var attributes = [];
+ var attribute;
+ for (var i = 0; i < changedAttributes.length; i += 1) {
+ attribute = changedAttributes[i];
+ attributes.push([attribute, element.attributes[attribute]]);
+ }
+ return attributes;
+ }
+
+ function addChangedStyles(element) {
+ var changedStyles = element._changedStyles;
+ var styles = [];
+ var style;
+ for (var i = 0; i < changedStyles.length; i += 1) {
+ style = changedStyles[i];
+ styles.push([style, element.style[style]]);
+ }
+ return styles;
+ }
+
+ function addChangedElements(element, elements) {
+ var changedElements = element._changedElements;
+ var elementsList = [];
+ var elementData;
+ for (var i = 0; i < changedElements.length; i += 1) {
+ elementData = changedElements[i];
+ elementsList.push([elementData[0].serialize(), elementData[1], elementData[2]]);
+ addElementToList(elementData[0], elements);
+ }
+ return elementsList;
+ }
+
+ function loadAnimation(payload) {
+ var params = payload.params;
+ var wrapper;
+ var animation;
+ var elements = [];
+ if (params.renderer === 'svg') {
+ wrapper = document.createElement('div');
+ params.container = wrapper;
+ } else {
+ var canvas = params.rendererSettings.canvas;
+ var ctx = canvas.getContext('2d');
+ params.rendererSettings.context = ctx;
+ }
+ animation = lottie.loadAnimation(params);
+ animation.addEventListener('error', function (error) {
+ console.log(error); // eslint-disable-line
+ });
+ animation.onError = function (error) {
+ console.log('ERRORO', error); // eslint-disable-line
+ };
+ if (params.renderer === 'svg') {
+ animation.addEventListener('DOMLoaded', function () {
+ var serialized = wrapper.serialize();
+ addElementToList(wrapper, elements);
+ self.postMessage({
+ type: 'loaded',
+ payload: {
+ id: payload.id,
+ tree: serialized.children[0],
+ totalFrames: animation.totalFrames,
+ frameRate: animation.frameRate,
+ },
+ });
+ });
+ animation.addEventListener('drawnFrame', function (event) {
+ var changedElements = [];
+ var element;
+ for (var i = 0; i < elements.length; i += 1) {
+ element = elements[i];
+ if (element._isDirty) {
+ var changedElement = {
+ id: element.attributes.id,
+ styles: addChangedStyles(element),
+ attributes: addChangedAttributes(element),
+ elements: addChangedElements(element, elements),
+ };
+ changedElements.push(changedElement);
+ element._isDirty = false;
+ element._changedAttributes.length = 0;
+ element._changedStyles.length = 0;
+ element._changedElements.length = 0;
+ }
+ }
+ self.postMessage({
+ type: 'updated',
+ payload: {
+ elements: changedElements,
+ id: payload.id,
+ currentTime: event.currentTime,
+ },
+ });
+ });
+ }
+ animations[payload.id] = animation;
+ }
+
+ return {
+ loadAnimation: loadAnimation,
+ };
+ }({}));
+ onmessage = function (evt) {
+ var data = evt.data;
+ var type = data.type;
+ var payload = data.payload;
+ if (type === 'load') {
+ lottieInternal.loadAnimation(payload);
+ } else if (type === 'pause') {
+ if (animations[payload.id]) {
+ animations[payload.id].pause();
+ }
+ } else if (type === 'play') {
+ if (animations[payload.id]) {
+ animations[payload.id].play();
+ }
+ } else if (type === 'stop') {
+ if (animations[payload.id]) {
+ animations[payload.id].stop();
+ }
+ } else if (type === 'setSpeed') {
+ if (animations[payload.id]) {
+ animations[payload.id].setSpeed(payload.value);
+ }
+ } else if (type === 'setDirection') {
+ if (animations[payload.id]) {
+ animations[payload.id].setDirection(payload.value);
+ }
+ } else if (type === 'setDirection') {
+ if (animations[payload.id]) {
+ animations[payload.id].setDirection(payload.value);
+ }
+ } else if (type === 'goToAndPlay') {
+ if (animations[payload.id]) {
+ animations[payload.id].goToAndPlay(payload.value, payload.isFrame);
+ }
+ } else if (type === 'goToAndStop') {
+ if (animations[payload.id]) {
+ animations[payload.id].goToAndStop(payload.value, payload.isFrame);
+ }
+ } else if (type === 'setSubframe') {
+ if (animations[payload.id]) {
+ animations[payload.id].setSubframe(payload.value);
+ }
+ } else if (type === 'addEventListener') {
+ if (animations[payload.id]) {
+ animations[payload.id].addEventListener(payload.eventName, function () {
+ self.postMessage({
+ type: 'event',
+ payload: {
+ id: payload.id,
+ callbackId: payload.callbackId,
+ argument: arguments[0],
+ },
+ });
+ });
+ }
+ } else if (type === 'destroy') {
+ if (animations[payload.id]) {
+ animations[payload.id].destroy();
+ animations[payload.id] = null;
+ }
+ } else if (type === 'resize') {
+ if (animations[payload.id]) {
+ animations[payload.id].resize();
+ }
+ }
+ };
+}
+
+function createWorker(fn) {
+ var blob = new Blob(['(' + fn.toString() + '())'], { type: 'text/javascript' });
+ var url = URL.createObjectURL(blob);
+ return new Worker(url);
+}
+// eslint-disable-next-line no-unused-vars
+var lottie = (function () {
+ 'use strict';
+
+ var workerInstance = createWorker(workerContent);
+ var animationIdCounter = 0;
+ var eventsIdCounter = 0;
+ var animations = {};
+ var defaultSettings = {
+ rendererSettings: {},
+ };
+
+ function createTree(data, container, map, afterElement) {
+ var elem;
+ if (data.type === 'div') {
+ elem = document.createElement('div');
+ } else {
+ elem = document.createElementNS(data.namespace, data.type);
+ }
+ for (var attr in data.attributes) {
+ if (Object.prototype.hasOwnProperty.call(data.attributes, attr)) {
+ if (attr === 'href') {
+ elem.setAttributeNS('http://www.w3.org/1999/xlink', attr, data.attributes[attr]);
+ } else {
+ elem.setAttribute(attr, data.attributes[attr]);
+ }
+ if (attr === 'id') {
+ map[data.attributes[attr]] = elem;
+ }
+ }
+ }
+ for (var style in data.style) {
+ if (Object.prototype.hasOwnProperty.call(data.style, style)) {
+ elem.style[style] = data.style[style];
+ }
+ }
+ data.children.forEach(function (element) {
+ createTree(element, elem, map);
+ });
+ if (!afterElement) {
+ container.appendChild(elem);
+ } else {
+ container.insertBefore(elem, afterElement);
+ }
+ }
+
+ var handleAnimationLoaded = (function () {
+ return function (payload) {
+ var animation = animations[payload.id];
+ animation._loaded = true;
+ // if callbacks have been added before the animation has loaded
+ animation.pendingCallbacks.forEach(function (callbackData) {
+ animation.animInstance.addEventListener(callbackData.eventName, callbackData.callback);
+ if (callbackData.eventName === 'DOMLoaded') {
+ callbackData.callback();
+ }
+ });
+ animation.animInstance.totalFrames = payload.totalFrames;
+ animation.animInstance.frameRate = payload.frameRate;
+ var container = animation.container;
+ var elements = animation.elements;
+ createTree(payload.tree, container, elements);
+ };
+ }());
+
+ function addNewElements(newElements, elements) {
+ var element;
+ for (var i = 0; i < newElements.length; i += 1) {
+ element = newElements[i];
+ var parent = elements[element[1]];
+ if (parent) {
+ var sibling;
+ if (element[2]) {
+ sibling = elements[element[2]];
+ }
+ createTree(element[0], parent, elements, sibling);
+ newElements.splice(i, 1);
+ i -= 1;
+ }
+ }
+ }
+
+ function updateElementStyles(element, styles) {
+ var style;
+ for (var i = 0; i < styles.length; i += 1) {
+ style = styles[i];
+ element.style[style[0]] = style[1];
+ }
+ }
+
+ function updateElementAttributes(element, attributes) {
+ var attribute;
+ for (var i = 0; i < attributes.length; i += 1) {
+ attribute = attributes[i];
+ element.setAttribute(attribute[0], attribute[1]);
+ }
+ }
+
+ function handleAnimationUpdate(payload) {
+ var changedElements = payload.elements;
+ var animation = animations[payload.id];
+ if (animation) {
+ var elements = animation.elements;
+ var elementData;
+ for (var i = 0; i < changedElements.length; i += 1) {
+ elementData = changedElements[i];
+ var element = elements[elementData.id];
+ addNewElements(elementData.elements, elements);
+ updateElementStyles(element, elementData.styles);
+ updateElementAttributes(element, elementData.attributes);
+ }
+ animation.animInstance.currentFrame = payload.currentTime;
+ }
+ }
+
+ function handleEvent(payload) {
+ var animation = animations[payload.id];
+ if (animation) {
+ var callbacks = animation.callbacks;
+ if (callbacks[payload.callbackId]) {
+ callbacks[payload.callbackId](payload.argument);
+ }
+ }
+ }
+
+ workerInstance.onmessage = function (event) {
+ if (event.data.type === 'loaded') {
+ handleAnimationLoaded(event.data.payload);
+ } else if (event.data.type === 'updated') {
+ handleAnimationUpdate(event.data.payload);
+ } else if (event.data.type === 'event') {
+ handleEvent(event.data.payload);
+ }
+ };
+
+ function resolveAnimationData(params) {
+ return new Promise(function (resolve, reject) {
+ var paramsCopy = Object.assign({}, defaultSettings, params);
+ if (paramsCopy.animType && !paramsCopy.renderer) {
+ paramsCopy.renderer = paramsCopy.animType;
+ }
+ if (paramsCopy.wrapper) {
+ if (!paramsCopy.container) {
+ paramsCopy.container = paramsCopy.wrapper;
+ }
+ delete paramsCopy.wrapper;
+ }
+ if (paramsCopy.animationData) {
+ resolve(paramsCopy);
+ } else if (paramsCopy.path) {
+ fetch(paramsCopy.path)
+ .then(function (response) {
+ return response.json();
+ })
+ .then(function (animationData) {
+ paramsCopy.animationData = animationData;
+ delete paramsCopy.path;
+ resolve(paramsCopy);
+ });
+ } else {
+ reject();
+ }
+ });
+ }
+
+ function loadAnimation(params) {
+ animationIdCounter += 1;
+ var animationId = 'lottie_animationId_' + animationIdCounter;
+ var animation = {
+ elements: {},
+ callbacks: {},
+ pendingCallbacks: [],
+ };
+ var animInstance = {
+ id: animationId,
+ pause: function () {
+ workerInstance.postMessage({
+ type: 'pause',
+ payload: {
+ id: animationId,
+ },
+ });
+ },
+ play: function () {
+ workerInstance.postMessage({
+ type: 'play',
+ payload: {
+ id: animationId,
+ },
+ });
+ },
+ stop: function () {
+ workerInstance.postMessage({
+ type: 'stop',
+ payload: {
+ id: animationId,
+ },
+ });
+ },
+ setSpeed: function (value) {
+ workerInstance.postMessage({
+ type: 'setSpeed',
+ payload: {
+ id: animationId,
+ value: value,
+ },
+ });
+ },
+ setDirection: function (value) {
+ workerInstance.postMessage({
+ type: 'setDirection',
+ payload: {
+ id: animationId,
+ value: value,
+ },
+ });
+ },
+ goToAndStop: function (value, isFrame) {
+ workerInstance.postMessage({
+ type: 'goToAndStop',
+ payload: {
+ id: animationId,
+ value: value,
+ isFrame: isFrame,
+ },
+ });
+ },
+ goToAndPlay: function (value, isFrame) {
+ workerInstance.postMessage({
+ type: 'goToAndPlay',
+ payload: {
+ id: animationId,
+ value: value,
+ isFrame: isFrame,
+ },
+ });
+ },
+ setSubframe: function (value) {
+ workerInstance.postMessage({
+ type: 'setSubframe',
+ payload: {
+ id: animationId,
+ value: value,
+ },
+ });
+ },
+ addEventListener: function (eventName, callback) {
+ if (!animation._loaded) {
+ animation.pendingCallbacks.push({
+ eventName: eventName,
+ callback: callback,
+ });
+ } else {
+ eventsIdCounter += 1;
+ var callbackId = 'callback_' + eventsIdCounter;
+ animation.callbacks[callbackId] = callback;
+ workerInstance.postMessage({
+ type: 'addEventListener',
+ payload: {
+ id: animationId,
+ callbackId: callbackId,
+ eventName: eventName,
+ },
+ });
+ }
+ },
+ destroy: function () {
+ animations[animationId] = null;
+ if (animation.container) {
+ animation.container.innerHTML = '';
+ }
+ workerInstance.postMessage({
+ type: 'destroy',
+ payload: {
+ id: animationId,
+ },
+ });
+ },
+ resize: function () {
+ workerInstance.postMessage({
+ type: 'resize',
+ payload: {
+ id: animationId,
+ },
+ });
+ },
+ };
+ animation.animInstance = animInstance;
+ resolveAnimationData(params)
+ .then(function (animationParams) {
+ var transferedObjects = [];
+ if (animationParams.container) {
+ animation.container = animationParams.container;
+ delete animationParams.container;
+ }
+ if (animationParams.renderer === 'canvas' && !animationParams.rendererSettings.canvas) {
+ var canvas = document.createElement('canvas');
+ animation.container.appendChild(canvas);
+ canvas.width = animationParams.animationData.w;
+ canvas.height = animationParams.animationData.h;
+ canvas.style.width = '100%';
+ canvas.style.height = '100%';
+ var offscreen = canvas.transferControlToOffscreen();
+ transferedObjects.push(offscreen);
+ animationParams.rendererSettings.canvas = offscreen;
+ }
+ animations[animationId] = animation;
+ workerInstance.postMessage({
+ type: 'load',
+ payload: {
+ params: animationParams,
+ id: animationId,
+ },
+ }, transferedObjects);
+ });
+ return animInstance;
+ }
+
+ var lottiejs = {
+ loadAnimation: loadAnimation,
+ };
+ return lottiejs;
+}());
diff --git a/rollup.config.js b/rollup.config.js
index 32794ed..b829979 100644
--- a/rollup.config.js
+++ b/rollup.config.js
@@ -1,7 +1,26 @@
import { nodeResolve } from '@rollup/plugin-node-resolve';
import { terser } from 'rollup-plugin-terser';
import babel from '@rollup/plugin-babel';
-import pkg from './package.json';
+
+// const copyFile = (options = {}) => {
+// const { targets = [], hook = 'buildEnd' } = options
+// return {
+// name: 'copy-file',
+// // [hook]: async(test) => {
+// // console.log('targets', targets.length)
+// // console.log('options', options)
+// // console.log('test', test)
+// // },
+// generateBundle: (options, bundle) => {
+// // console.log('PASO,. generateBundle', options, bundle)
+// console.log('PASO,. generateBundle', options, bundle.code)
+// // bundle.code = '()=>{}';
+// },
+// writeBundle: () => {
+// console.log('PASO,. writeBundle')
+// }
+// }
+// }
const destinationBuildFolder = 'build/player/';
@@ -169,7 +188,6 @@
acc = acc.concat(builds);
return acc;
}, []);
-console.log('exports', exports.length);
export default exports;
/* export default [
{
diff --git a/tasks/build_worker.js b/tasks/build_worker.js
new file mode 100644
index 0000000..5d146e7
--- /dev/null
+++ b/tasks/build_worker.js
@@ -0,0 +1,163 @@
+const fs = require('fs');
+const UglifyJS = require("uglify-js");
+
+const buildFolder = 'build/player/';
+const rootFolder = 'player/';
+const bm_version = '5.8.1';
+const defaultBuilds = [ 'canvas_worker', 'lottie_worker']
+
+const scripts = [
+
+]
+
+function wrapScriptWithModule(code, build) {
+ return new Promise((resolve, reject)=>{
+ try {
+ // Wrapping with module
+ let moduleFileName = 'worker_wrapper';
+ let wrappedCode = fs.readFileSync(`${rootFolder}js/${moduleFileName}.js`, "utf8");
+ wrappedCode = wrappedCode.replace('/* <%= contents %> */',code);
+ wrappedCode = wrappedCode.replace('[[BM_VERSION]]',bm_version);
+ resolve(wrappedCode);
+ } catch(err) {
+ reject(err);
+ }
+ });
+}
+
+function uglifyCode(code) {
+ return new Promise((resolve, reject)=>{
+ try {
+ const result = UglifyJS.minify(code, {
+ output:
+ {
+ ascii_only:true
+ },
+ toplevel:true,
+ mangle: {
+ reserved: ['lottie']
+ }
+ });
+ if (result.error) {
+ reject(result.error)
+ } else {
+ resolve(result.code)
+ }
+ } catch(err) {
+ reject(err)
+ }
+ })
+}
+
+async function modularizeCode(code, build) {
+ const globalScope = (build =='canvas_worker' || build =='lottie_worker') ? 'self' : 'window'
+ return `(typeof navigator !== "undefined") && (function(root, factory) {
+ if (typeof define === "function" && define.amd) {
+ define(function() {
+ return factory(root);
+ });
+ } else if (typeof module === "object" && module.exports) {
+ module.exports = factory(root);
+ } else {
+ root.lottie = factory(root);
+ root.bodymovin = root.lottie;
+ }
+}((${globalScope} || {}), function(window) {
+ ${code}
+return lottie;
+}));`
+}
+
+async function getCode() {
+ try {
+ let scriptsString = '';
+ scriptsString += fs.readFileSync(`${buildFolder}lottie.js`, {encoding: 'utf8'});
+ scriptsString += '\r\n';
+ return scriptsString;
+ } catch(err) {
+ throw err;
+ }
+}
+
+async function buildVersion(version) {
+ try {
+ const code = await getCode(version.build)
+ const wrappedCode = await wrapScriptWithModule(code, version.build)
+ const processedCode = await version.process(wrappedCode)
+ const modularizedCode = await modularizeCode(processedCode, version.build)
+ const saved = await save(modularizedCode, version.fileName)
+ return saved
+ } catch (error) {
+ console.log(error);
+ return null;
+ }
+}
+
+function save(code, fileName) {
+ return new Promise((resolve, reject)=> {
+ fs.writeFile(`${buildFolder}${fileName}`, code, (err) => {
+ if (err) {
+ reject(err)
+ } else {
+ resolve(true)
+ }
+ });
+ })
+}
+
+function noop(code) {
+ return Promise.resolve(code)
+}
+
+function buildVersions() {
+ return new Promise((resolve, reject) => {
+ let versions = [
+ {
+ fileName: 'lottie_canvas_worker.js',
+ build: 'canvas_worker',
+ process: noop
+ },
+ {
+ fileName: 'lottie_canvas_worker.min.js',
+ build: 'canvas_worker',
+ process: uglifyCode
+ },
+ {
+ fileName: 'lottie_worker.js',
+ build: 'lottie_worker',
+ process: noop
+ },
+ {
+ fileName: 'lottie_worker.min.js',
+ build: 'lottie_worker',
+ process: uglifyCode
+ }
+ ];
+
+ const buildProcesses = versions.map((version)=>{
+ return buildVersion(version)
+ })
+ Promise.all(buildProcesses)
+ .then(() => {
+ resolve('Build Process Ended')
+ })
+ .catch((err)=>{
+ reject(err)
+ })
+ })
+}
+
+function handleError(err) {
+ console.log(err);
+}
+
+async function build() {
+ try {
+ const result = await buildVersions();
+
+ } catch(err) {
+ handleError(err);
+ }
+}
+
+build()