{"id":19212347,"url":"https://github.com/leafo/lapis-eswidget","last_synced_at":"2025-10-20T04:01:50.360Z","repository":{"id":63729920,"uuid":"568546005","full_name":"leafo/lapis-eswidget","owner":"leafo","description":"A widget base class designed for generating ES modules for bundling JavaScript \u0026 more","archived":false,"fork":false,"pushed_at":"2024-06-07T18:11:29.000Z","size":164,"stargazers_count":5,"open_issues_count":4,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-06-08T11:51:19.784Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"MoonScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/leafo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-11-20T21:20:37.000Z","updated_at":"2024-07-27T03:52:16.000Z","dependencies_parsed_at":"2024-06-07T19:50:31.124Z","dependency_job_id":null,"html_url":"https://github.com/leafo/lapis-eswidget","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/leafo/lapis-eswidget","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leafo%2Flapis-eswidget","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leafo%2Flapis-eswidget/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leafo%2Flapis-eswidget/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leafo%2Flapis-eswidget/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/leafo","download_url":"https://codeload.github.com/leafo/lapis-eswidget/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leafo%2Flapis-eswidget/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266433103,"owners_count":23927700,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-22T02:00:09.085Z","response_time":66,"last_error":null,"robots_txt_status":null,"robots_txt_updated_at":null,"robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-09T13:46:38.765Z","updated_at":"2025-10-20T04:01:50.255Z","avatar_url":"https://github.com/leafo.png","language":"MoonScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# lapis-eswidget\n\nThis library provides a base Widget class that enables aggregation of static\nJavaScript code and a unified system for initializing a widget with JavaScript\ncode.\n\nA command-line tool is included for compiling a widget modules (`compile_js`)\ninto an [ES\nModules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules),\nand for building build scripts (`generate_spec`) to compile bundles for groups\nof code using something like [esbuild](https://esbuild.github.io/).\n\n## Example\n\n```moonscript\n-- widgets/my_widget.moon\nclass MyWidget extends require \"lapis.eswidget\"\n  @asset_packages: {\"main\"}\n\n  @es_module: [[\n    import CoolThing from \"./cool_thing\"\n    new CoolThing(widget_selector)\n  ]]\n\n  inner_content: =\u003e\n    div \"Hi\"\n\n```\n\nExtracting the module code on the command line:\n\n```bash\nlapis-eswidget compile_js --module widgets.my_widget\n```\n\nExtracting module code in code:\n\n```moonscript\nMyWidget = require(\"widgets.my_widget\")\n\nprint MyWidget\\compile_es_module!\n```\n\nBuilding an entire package the module code on the command line:\n\n\u003e Note: You generally want to use `generate_spec` to create instructions to\n\u003e build a package incrementally. --package mode may be slow since it will scan\n\u003e and evaluate the entire widget filesystem tree\n\n```bash\nlapis-eswidget compile_js --package main\n```\n\n## `lapis-eswidget` command line tool\n\nThe `lapis-eswidget` command can be used to work with widget modules,\nextracting code or generating instructions to create the final bundles.\n\n    Usage: lapis-eswidget [-h] [--moonscript] \u003ccommand\u003e ...\n\n    Widget asset compilation and build generation\n\n    Options:\n       -h, --help            Show this help message and exit.\n       --moonscript          Enable MoonScript module loading\n\n    Commands:\n       compile_js            Compile a single module or entire package to JavaScript\n       generate_spec         Scan widgets and generate specification for compiling bundles\n       debug                 Show any extractable information about a widget module\n\nThe following commands are included\n\n### `compile_js`\n\n```\nlapis-eswidget compile_js --help\n\nUsage: lapis-eswidget compile_js [-h] [--module \u003cmodule\u003e]\n       [--file \u003cfile\u003e] [--package \u003cpackage\u003e]\n       [--widget-dirs \u003cwidget_dirs\u003e]\n\nCompile a single module or entire package to JavaScript\n\nOptions:\n   -h, --help            Show this help message and exit.\n   --module \u003cmodule\u003e\n   --file \u003cfile\u003e\n   --package \u003cpackage\u003e\n   --widget-dirs \u003cwidget_dirs\u003e\n                         Paths where widgets are located. Only used for compiling by --package (default: views,widgets)\n\n```\n\nCompile a single module or entire package to JavaScript. One of the following\nsources must be specified:\n\n* `--file` - Load by the filename of a Lua module that contains an ESWidget (eg. `views/profile.lua`)\n* `--module` - Load by Lua module name (eg. `views.profile`)\n* `--package` - Will scan filesystem (see `--widget-dirs`) and concatenate the output of all Lua modules that extend ESWidget and specify the package in `@asset_packages`\n\nIf you want to enable loading MoonScript modules then you must pass `--moonscript`\n\n### `generate_spec`\n\n```\nUsage: lapis eswidget generate_spec [-h] [--minify {both,only,none}]\n       [--skip-bundle] [--css-packages \u003ccss_packages\u003e]\n       [--bundle-method {esbuild,module,concat}]\n       [--widget-dirs \u003cwidget_dirs\u003e] [--format {json,tup,makefile}]\n       [--source-dir \u003csource_dir\u003e] [--output-dir \u003coutput_dir\u003e]\n       [--esbuild-metafile] [--esbuild-bin \u003cesbuild_bin\u003e]\n       [--esbuild-args \u003cesbuild_args\u003e] [--sourcemap]\n       [--tup-compile-dep-group \u003ctup_compile_dep_group\u003e]\n       [--tup-bundle-dep-group \u003ctup_bundle_dep_group\u003e]\n       [--tup-compile-out-group \u003ctup_compile_out_group\u003e]\n       [--tup-bundle-out-group \u003ctup_bundle_out_group\u003e]\n\nScan widgets and generate specification for compiling bundles\n\nPrimary options:\n   --bundle-method {esbuild,module,concat}\n                         What tool to use to bundle the packages (default: esbuild)\n   --widget-dirs \u003cwidget_dirs\u003e\n                         Paths where widgets are located (default: views,widgets)\n   --format {json,tup,makefile}\n                         Output fromat for generated asset spec file (default: json)\n   --source-dir \u003csource_dir\u003e\n                         The working directory for source files (NODE_PATH will be set to this during bundle) (default: static/js)\n   --output-dir \u003coutput_dir\u003e\n                         Destination of final compiled asset packages (default: static)\n\nesbuild:\n   --esbuild-metafile, --metafile\n                         Enable esbuild metafile, creates {output}-metafile.json for every bundled output\n   --esbuild-bin \u003cesbuild_bin\u003e\n                         Set the path to the esbuild binary. When empty, will use the ESBUILD tup environment variable\n   --esbuild-args \u003cesbuild_args\u003e\n                         Append additional arguments to esbuild command\n   --sourcemap           Enable sourcemap for bundled outputs (esbuild only)\n\ntup:\n   --tup-compile-dep-group \u003ctup_compile_dep_group\u003e\n                         Dependency group used during the widget -\u003e js compile phase (eg. $(TOP)/\u003cmoon\u003e)\n   --tup-bundle-dep-group \u003ctup_bundle_dep_group\u003e\n                         Dependency group used during esbuild bundling phase (eg. $(TOP)/\u003ccoffee\u003e)\n   --tup-compile-out-group \u003ctup_compile_out_group\u003e\n                         Which group name to place compile output files in (eg. $(TOP)/\u003cmodules\u003e)\n   --tup-bundle-out-group \u003ctup_bundle_out_group\u003e\n                         Which group name to place bundle output files in (eg. $(TOP)/\u003cbundles\u003e)\n\nOther options:\n   -h, --help            Show this help message and exit.\n   --minify {both,only,none}\n                         Set how minified bundles should be generated (default: both)\n   --skip-bundle         Skip generated final bundling command\n   --css-packages \u003ccss_packages\u003e\n                         Instruct build that css files will be generated for listed packages\n\n```\n\nScan directories for widgets that extend from `ESWidget` and generate a\nspecification for compiling bundles. This intermediate file is called an *Asset\nSpec*.\n\nSupports the following output formats: `json`, `tup`, `makefile`\n\nThe generated build script bundles in two phases:\n\n1. Individual widgets are compiled to their JavaScript Module counterpart using the `compile_js` command above. The output of this command is placed dirctly next to the Lua file for the widget. eg. `widgets/hello.lua` will generate `widgets/hello.js` if it defines an `es_module` and `asset_packages` field.\n2. Every `.js` file generated is aggregated into 0 or more packages for bundling. For each asset package, the list of input files is piped into the bundling command. The default bundling method is `esbuild`.\n\nThe bundling phase can be disabled with `--skip-bundle`. This can be useful if\nyou wish to explicitly import the widget code you want in your bundling system.\n\nThe `module` bundle method can be used to aggregate a list of `import`\nstatements in a single file that can be imported elsewhere.\n\n### `debug`\n\n```\nlapis-eswidget debug --help\n```\n\nDisplay information about a single widget\n\n## `ESWidget` base class\n\nAny widgets you wish to be supported by this library must extend from\n`ESWidget`.\n\n```moonscript\nESWidget = require \"lapis.eswidget\"\n```\n\n\n### Inerface\n\nClass\n\n* `@asset_packages` (array table, default: `{}`) - The packages that this widget's assets will be placed into\n* `@widget_name` (function) - Returns a name for the widget used for class names and file names (eg. MyWidget -\u003e my_widget)\n* `@widget_class_name` (function) - Returns the CSS class name of this widget as a string\n* `@widget_class_list` (function) - Return variable number of arguments for the list of CSS classes this widget will have when rendered, calculated from the inheritance chain\n* `@compile_es_module` (function) - Compile the static `es_module` initialization code for the widget\n\nClass properties\n\n* `@es_module` (string, default: `nil`) - The initialization JavaScript for the widget\n* `@prop_types` (table, default: `nil`) - Enables property validation for the widget, with a table mapping names to a tableshape type\n\nInstance\n\n* `widget_id()` (function) - Returns a string with a unique ID for the widget, of the format `{widget_name}_{random_number}`\n* `widget_selector()` (function) - Returns a snippet of JavaScript that can be used to uniquely identify the element on the page\n* `widget_enclosing_element` (string, default: `\"div\"`)\n* `widget_enclosing_attributes()` (function) - Returns a table of attributes to be used on the enclosing element. By default will include autogenerated class names and autogenerated ID\n* `js_init(params)` (function) - Returns JavaScript code as a string that will be embedded with `raw` to initialize the widget on the page. `params` will be json_encoded if called with override\n* `content()` (function) - Renders the enclosing widget, containing `inner_content`, and appens any javascript initialization by calling `render_js_init`\n* `render_js_init()` (function) - Adds script tag/`content_for` call to append the contents of `js_init` to the page. This is automatically called by the default content method\n* `inner_content` (function, default: empty function) - The render function of the widget called inside of the enclosing element\n\n### Static vs Instance code\n\nThere are two kinds of data associated with each widget during it's render\nlifecycle:\n\n**Static** code and data is unchanging and can be compiled and used during the\nahead-of-time building of packages. This includes things like the ES Module\ninitialization function (`@@es_module`), CSS classnames.\n\n**Instance** code and data is only available during the rendering of a widget\nduring a request. This could include things like the dynamically created widget\nID to uniquely referencing its element on a page, parameters to JavaScript\ninitialization.\n\n### HTML Encapsulation\n\nThe `ESWidget` class provides a default `content` method that will\nautomatically generate a class and ID for an HTML element to allow it to be\nuniquely identified by JavaScript initialization, and generally identified by CSS\nselectors.\n\nTo user encapsulation, the `inner_content` method must be implemented instead\nof the `content` method on the widget sub-class, otherwise the enclosing\nelement logic will be overwritten.\n\nThe generated class names will utilize the entire class hierarchy:\n\n```moonscript\nclass One extends require \"lapis.eswidget\"\nclass Two extends One\nclass Three extends Two\n\n\nOne\\widget_class_list! --\u003e  \"one_widget\"\nTwo\\widget_class_list! --\u003e \"two_widget\", \"one_widget\"\nThree\\widget_class_list! --\u003e  \"three_widget\", \"two_widget\", \"one_widget\"\n```\n\n### Parameter Validation\n\nThe base ESWidget class has a mechanism to validate inputs passed into the\nWidget. The `prop_types` field takes a table of names and *tableshape* types\nto be used to validate the values of the inputs.\n\n\n```moonscript\nclass MyThing extends require \"lapis.eswidget\"\n  @prop_types: {\n    name: types.string -- this is a required input\n    banned: types.boolean\\is_optional!\n  }\n\n  inner_content: =\u003e\n    h2 @props.name\n    if @props.banned\n      p \"You are banned\"\n    else\n      p \"You are not banned\"\n\nwidget1 = MyThing name: \"Cool\", banned: true\n\nwidget2 = MyThing name: 2323 --\u003e this will fail with an error\n```\n\nProviding `@prop_types` will change the default behavior of the constructor. As\na reminder, the default `Widget` constructor copies every field from the\nargument object onto the widget instance. When `@prop_types` is used, the\ninputs will be validated and collected into an object called `props` that will\nbe stored on the widget instance.  Eg. you would access name with `@props.name`\ninstead of `@name` in the example above.\n\nBy default `prop_types` will only validate the object passed into the\nconstructor.  A widget can actually receive a second source of inputs though.\nIn Lapis, when a widget renders, an internal helper chain is set that includes\na reference to the *Request* object. This is how you can access things like\n`@url_for`, and it is also how you access fields that are set during the\nrequest action handler.\n\nIn order to validate render-time inputs, you must flag the prop type with the\n`render_prop` function. This will allow the prop_type to validate from the\nrender helper chain if the value was not provided directly to the constructor.\nThe result will be copied into the `props` field regardless.\n\n\n```moonscript\nimport types from require \"tableshape\"\nimport render_prop from require \"lapis.eswidget.prop_types\"\n\nclass UserProfile extends require \"lapis.eswidget\"\n  @prop_types: {\n    language: types.string -- this is a required input\n    user: render_prop types.table\n  }\n\n\n-- the `user` field provided in the constructor will take precedence. No\n-- additional validation is done at render time\nprofile1 = UserProfile language: \"en\", user: {id: 10}\n\n-- the `user` field here will be validated when the widget is rendered, so this\n-- will not throw an error\nprofile2 = UserProfile language: \"en\"\n```\n\n### Asset Packages\n\nThe `asset_packages` class field is an array of package names that a widget's\nassets should be aggregated into when bundling. No asset package names are set\nby default, if you wish to aggregate assets then you will need to provide at\nleast one asset package.\n\nThe end result of bundling will result in a file (or files) containing output\nfrom widgets that target that package, eg. `main` → `main.js`, `main.css`\n\nMultiple asset packages can be used for splitting code at a high level to\nreduce total bundle sizes.\n\nThe first asset package in the list of asset packages is used to calculate the\ncanonical path for Associated Files (see below).\n\n### Associated Files\n\n**TODO**: This is not exposed currently\n\nAn associated file is a file related to the widget that is manually written (as\nopposed to generated by the build system). These files have code\nimplementations of logic that is too big to be placed directly into the Widget\nclass declaration.\n\nThe naming convention is: \n\n`/static/{asset_type}/{asset_package}/{widget_path}.{ext}`\n\n* Where `asset_type` is like `css`, `js`, `scss`, `coffee` etc.\n* Where `asset_package` is the first package specified by the widget, like `main`, `admin`, etc. (Packages are user-defined and can be anything)\n* Where `widget_path` the conversion of the widget's module name to a path, like `widgets.hello.world` -\u003e `hello/world` (Note the module prefix is not included)\n* And `ext` is the appropriate extension for the file\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fleafo%2Flapis-eswidget","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fleafo%2Flapis-eswidget","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fleafo%2Flapis-eswidget/lists"}