├── .node-version ├── source ├── CNAME ├── basic │ ├── async │ │ ├── example │ │ │ ├── async-await.js │ │ │ ├── dummyFetch.js │ │ │ └── callback-chain.js │ │ ├── img │ │ │ ├── promise-chain.png │ │ │ ├── promise-chain.graffle │ │ │ ├── single-thread-tasks.png │ │ │ ├── then-rejected-promise.png │ │ │ ├── async-single-thread-tasks.png │ │ │ └── block--async-single-thread-tasks.png │ │ └── demo │ │ │ ├── try-catch-Function-callback.js │ │ │ ├── try-catch-sync-callback.js │ │ │ ├── try-catch-new-promise.js │ │ │ ├── try-catch-new-promise-re-throw.js │ │ │ ├── try-catch-error-in-sync-callback.js │ │ │ ├── try-catch-setTimeout-callback.js │ │ │ ├── try-catch-error-generator.js │ │ │ └── Task-error.js │ ├── read-eval-print │ │ ├── src │ │ │ ├── example │ │ │ │ ├── index1.js │ │ │ │ ├── index.js │ │ │ │ └── index.html │ │ │ ├── console-example.js │ │ │ ├── invalid │ │ │ │ ├── reference-error.js │ │ │ │ ├── syntax-error-typo │ │ │ │ │ ├── index.js │ │ │ │ │ └── index.html │ │ │ │ └── syntax-error │ │ │ │ │ ├── index.js │ │ │ │ │ └── index.html │ │ │ ├── console-expression-example.js │ │ │ ├── console-variable-example.js │ │ │ ├── runtime-error │ │ │ │ ├── index.js │ │ │ │ └── index.html │ │ │ └── empty │ │ │ │ └── index.html │ │ ├── img │ │ │ ├── web-console.png │ │ │ └── syntax-error.png │ │ ├── screenshot.sh │ │ └── OUTLINE.md │ ├── data-type │ │ └── src │ │ │ ├── var-null-invalid.js │ │ │ ├── multiple-line-string-invalid.js │ │ │ └── octal-legacy-literal-invalid.js │ ├── module │ │ ├── src │ │ │ ├── math-utils.js │ │ │ ├── data.json │ │ │ ├── my-module-2.js │ │ │ ├── side-effects.js │ │ │ ├── my-module-1.js │ │ │ ├── named-export-declare.js │ │ │ ├── default-export.js │ │ │ ├── import-side-effects.js │ │ │ ├── named-export.js │ │ │ ├── default-export-alias.js │ │ │ ├── default-export-variables-invalid.js │ │ │ ├── my-module.js │ │ │ ├── named-export-alias.js │ │ │ ├── default-import.js │ │ │ ├── named-import-alias.js │ │ │ ├── default-import-alias.js │ │ │ ├── import-attributes.js │ │ │ ├── named-import.js │ │ │ ├── default-import-with-named.js │ │ │ ├── namespace-import.js │ │ │ └── re-export-invalid.js │ │ └── test │ │ │ ├── export-4.js │ │ │ ├── export-2.js │ │ │ ├── export-5.js │ │ │ ├── export-1.js │ │ │ └── export-3.js │ ├── error-try-catch │ │ ├── src │ │ │ ├── error.js │ │ │ ├── console │ │ │ │ ├── index.js │ │ │ │ └── index.html │ │ │ ├── error.html │ │ │ └── error-cause │ │ │ │ ├── index.html │ │ │ │ └── index.js │ │ ├── img │ │ │ ├── error.png │ │ │ ├── error-cause.png │ │ │ └── console.error.png │ │ └── screenshot-manual.sh │ ├── object │ │ ├── src │ │ │ ├── prop-invalid.js │ │ │ ├── prop-dot-invalid.js │ │ │ └── freeze-property-invalid.js │ │ └── OUTLINE.md │ ├── function-scope │ │ └── src │ │ │ └── identifier-duplicated-invalid.js │ ├── loop │ │ ├── src │ │ │ ├── do-while │ │ │ │ └── do-while-example.js │ │ │ ├── for-of │ │ │ │ ├── for-of-array-example.js │ │ │ │ └── for-of-string-example.js │ │ │ ├── break │ │ │ │ ├── some-even-example.js │ │ │ │ ├── break-find-example.js │ │ │ │ ├── find-even-return-example.js │ │ │ │ └── find-even-break-example.js │ │ │ ├── while │ │ │ │ └── while-add-example.js │ │ │ ├── continue │ │ │ │ ├── filter-even-example.js │ │ │ │ └── continue-filter-even-example.js │ │ │ ├── for │ │ │ │ ├── sum-reduce-example.js │ │ │ │ ├── sum-for-each-example.js │ │ │ │ └── sum-for-example.js │ │ │ └── for-in │ │ │ │ ├── for-in-array-bug-example.js │ │ │ │ ├── object-keys-for-each-example.js │ │ │ │ └── for-in-object-example.js │ │ ├── public │ │ │ ├── index.js │ │ │ └── index.html │ │ └── test │ │ │ ├── do-while │ │ │ └── do-while-example-test.js │ │ │ ├── for-of │ │ │ ├── for-of-array-example-test.js │ │ │ └── for-of-string-example-test.js │ │ │ ├── for-in │ │ │ ├── for-in-array-bug-example-test.js │ │ │ ├── for-in-object-example-test.js │ │ │ └── object-keys-for-each-example-test.js │ │ │ └── while │ │ │ └── while-add-example-test.js │ ├── string │ │ ├── src │ │ │ ├── multiline-invalid.js │ │ │ └── invalid │ │ │ │ ├── regexp-literal.js │ │ │ │ └── regexp-constructor.js │ │ └── specs │ │ │ └── add-regexp-escape-docs │ │ │ └── requirements.md │ ├── array │ │ ├── img │ │ │ └── array.prototype.slice.png │ │ └── src │ │ │ └── const-empty-array-invalid.js │ ├── function-declaration │ │ └── src │ │ │ └── arguments.js │ ├── condition │ │ ├── src │ │ │ ├── if │ │ │ │ ├── if-example.js │ │ │ │ ├── else-if-example.js │ │ │ │ ├── leap-year-flat-example.js │ │ │ │ └── leap-year-nest-example.js │ │ │ └── switch │ │ │ │ ├── switch-example.js │ │ │ │ ├── switch-return-example.js │ │ │ │ └── miss-case-example.js │ │ └── test │ │ │ └── swtich │ │ │ ├── switch-example-test.js │ │ │ └── miss-case-example-test.js │ ├── variables │ │ └── src │ │ │ ├── const-without-assign-invalid.js │ │ │ ├── const-do-not-assign-invalid.js │ │ │ └── let-duplicated-define-invalid.js │ ├── introduction │ │ └── img │ │ │ ├── javascript-ecmascript.png │ │ │ └── javascript-ecmascript.xml │ ├── prototype-object │ │ ├── img │ │ │ ├── object-prototype.png │ │ │ └── object-prototype.graffle │ │ └── OUTLINE.md │ ├── string-unicode │ │ ├── img │ │ │ ├── codeunit-codepoint-table.png │ │ │ ├── emoji-codeunit-codepoint-table.png │ │ │ ├── extenal-code-and-internal-code.png │ │ │ ├── codeunit-codepoint-table.txt │ │ │ ├── emoji-codeunit-codepoint.txt │ │ │ ├── codeunit-codepoint-table.xml │ │ │ └── emoji-codeunit-codepoint-table.xml │ │ └── Unicode-Column.md │ ├── function-this │ │ ├── src │ │ │ └── this-is-readonly-invalid.js │ │ └── this.js │ ├── statement-expression │ │ └── src │ │ │ └── statement-not-expression-invalid.js │ ├── implicit-coercion │ │ └── img │ │ │ └── JavaScript-Equality-Table.png │ ├── operator │ │ └── img │ │ │ ├── 0000_0000_0000_0000_0000_0000_0000_0001.png │ │ │ └── 1111_1111_1111_1111_1111_1111_1111_1111.png │ ├── json │ │ └── src │ │ │ └── circular-reference.js │ ├── class │ │ └── example │ │ │ ├── Counter.js │ │ │ ├── Point.js │ │ │ ├── ConflictClass.js │ │ │ ├── Car.js │ │ │ ├── Prototype.js │ │ │ ├── prototype-call.js │ │ │ ├── EventEmitter.js │ │ │ ├── MyClass.js │ │ │ ├── Square.js │ │ │ └── ObservableValue.js │ └── iterator-generator │ │ └── examples │ │ ├── basic │ │ ├── array-vs-iterator.example.js │ │ ├── iterator.example.js │ │ ├── iterator-example-test.js │ │ └── array-vs-iterator-example-test.js │ │ └── protocol │ │ ├── manual-iteration.example.js │ │ ├── manual-iteration-example-test.js │ │ └── create-range.example.js ├── use-case │ ├── ajaxapp │ │ ├── test │ │ │ └── index-test.js │ │ ├── entrypoint │ │ │ ├── example │ │ │ │ ├── index.js │ │ │ │ └── index.html │ │ │ ├── img │ │ │ │ └── fig-1.png │ │ │ └── screenshot.sh │ │ ├── http │ │ │ ├── img │ │ │ │ ├── fig-1.png │ │ │ │ └── fig-2.png │ │ │ ├── example │ │ │ │ ├── index.html │ │ │ │ └── index.js │ │ │ └── screenshot-manual.sh │ │ ├── display │ │ │ ├── img │ │ │ │ └── fig-1.png │ │ │ └── example │ │ │ │ └── index.html │ │ ├── promise │ │ │ ├── img │ │ │ │ └── fig-1.png │ │ │ ├── example │ │ │ │ └── index.html │ │ │ └── screenshot-manual.sh │ │ ├── src │ │ │ └── index.html │ │ ├── xhr │ │ │ └── index.html │ │ ├── package.json │ │ └── README.md │ ├── nodecli │ │ ├── read-file │ │ │ └── src │ │ │ │ ├── sample.md │ │ │ │ ├── package-lock.json │ │ │ │ ├── package.json │ │ │ │ ├── main-2.js │ │ │ │ ├── main-1.js │ │ │ │ └── main-3.js │ │ ├── helloworld │ │ │ └── src │ │ │ │ ├── main.js │ │ │ │ └── package.json │ │ ├── test │ │ │ └── snapshots │ │ │ │ ├── helloworld_src_main.js.txt │ │ │ │ ├── read-file_src_main-2.js.txt │ │ │ │ ├── read-file_src_main-3.js.txt │ │ │ │ ├── argument-parse_src_main-2.js.txt │ │ │ │ ├── read-file_src_main-1.js.txt │ │ │ │ ├── md-to-html_src_main.js.txt │ │ │ │ ├── md-to-html_src_main-2.js.txt │ │ │ │ ├── md-to-html_src_main-3.js.txt │ │ │ │ ├── refactor-and-unittest_src_main.js.txt │ │ │ │ ├── refactor-and-unittest_src_main-last.js.txt │ │ │ │ ├── md-to-html_src_main-1.js.txt │ │ │ │ ├── md-to-html_src_main.js--gfm.txt │ │ │ │ ├── md-to-html_src_main-3.js--gfm.txt │ │ │ │ ├── refactor-and-unittest_src_main.js--gfm.txt │ │ │ │ └── refactor-and-unittest_src_main-last.js--gfm.txt │ │ ├── refactor-and-unittest │ │ │ └── src │ │ │ │ ├── test │ │ │ │ ├── .editorconfig │ │ │ │ ├── fixtures │ │ │ │ │ ├── sample.md │ │ │ │ │ ├── expected.html │ │ │ │ │ └── expected-gfm.html │ │ │ │ └── md2html-test.js │ │ │ │ ├── example │ │ │ │ ├── greet-main.js │ │ │ │ └── greet.js │ │ │ │ ├── sample.md │ │ │ │ ├── md2html.js │ │ │ │ ├── package.json │ │ │ │ ├── package-lock.json │ │ │ │ ├── main.js │ │ │ │ └── main-last.js │ │ ├── argument-parse │ │ │ └── src │ │ │ │ ├── main-1.js │ │ │ │ ├── package-lock.json │ │ │ │ ├── main-2.js │ │ │ │ └── package.json │ │ ├── md-to-html │ │ │ └── src │ │ │ │ ├── sample.md │ │ │ │ ├── package.json │ │ │ │ ├── main-2.js │ │ │ │ ├── main-1.js │ │ │ │ ├── main-3.js │ │ │ │ ├── main.js │ │ │ │ └── package-lock.json │ │ ├── bootstrap.sh │ │ ├── package.json │ │ └── README.md │ ├── setup-local-env │ │ ├── src │ │ │ ├── index.js │ │ │ └── index.html │ │ ├── img │ │ │ └── index.png │ │ └── screenshot.sh │ ├── todoapp │ │ ├── cypress.json │ │ ├── entrypoint │ │ │ ├── first-entry │ │ │ │ ├── index.js │ │ │ │ ├── package.json │ │ │ │ ├── index.html │ │ │ │ └── screenshot.sh │ │ │ ├── module-entry │ │ │ │ ├── index.js │ │ │ │ ├── package.json │ │ │ │ ├── src │ │ │ │ │ └── App.js │ │ │ │ └── index.html │ │ │ ├── img │ │ │ │ └── first-entry.png │ │ │ └── module-scope │ │ │ │ └── index.html │ │ ├── app-structure │ │ │ ├── todo-html │ │ │ │ ├── index.js │ │ │ │ ├── src │ │ │ │ │ └── App.js │ │ │ │ ├── screenshot.sh │ │ │ │ └── index.html │ │ │ └── img │ │ │ │ └── todo-html.png │ │ ├── final │ │ │ ├── final │ │ │ │ ├── index.js │ │ │ │ ├── src │ │ │ │ │ ├── model │ │ │ │ │ │ ├── TodoItemModel.example.js │ │ │ │ │ │ ├── TodoItemModel.js │ │ │ │ │ │ └── TodoListModel.example.js │ │ │ │ │ └── view │ │ │ │ │ │ ├── TodoItemView.example.js │ │ │ │ │ │ └── TodoListView.js │ │ │ │ └── index.html │ │ │ ├── create-view │ │ │ │ ├── index.js │ │ │ │ ├── src │ │ │ │ │ ├── model │ │ │ │ │ │ ├── TodoItemModel.example.js │ │ │ │ │ │ ├── TodoItemModel.js │ │ │ │ │ │ └── TodoListModel.example.js │ │ │ │ │ └── view │ │ │ │ │ │ ├── TodoItemView.sample.js │ │ │ │ │ │ ├── TodoItemView.example.js │ │ │ │ │ │ └── TodoListView.js │ │ │ │ └── index.html │ │ │ └── more │ │ │ │ ├── index.js │ │ │ │ ├── src │ │ │ │ ├── model │ │ │ │ │ └── TodoItemModel.js │ │ │ │ └── view │ │ │ │ │ └── TodoListView.js │ │ │ │ └── index.html │ │ ├── event-model │ │ │ └── event-emitter │ │ │ │ ├── index.js │ │ │ │ ├── src │ │ │ │ ├── EventEmitter.example.js │ │ │ │ └── model │ │ │ │ │ ├── TodoItemModel.example.js │ │ │ │ │ ├── TodoItemModel.js │ │ │ │ │ ├── TodoListModel.example.js │ │ │ │ │ └── TodoListModel.js │ │ │ │ └── index.html │ │ ├── form-event │ │ │ ├── add-todo-item │ │ │ │ ├── index.js │ │ │ │ ├── src │ │ │ │ │ ├── view │ │ │ │ │ │ ├── html-util-element-sample.js │ │ │ │ │ │ └── html-util-render-sample.js │ │ │ │ │ └── App.js │ │ │ │ ├── index.html │ │ │ │ └── screenshot.sh │ │ │ ├── prevent-event │ │ │ │ ├── index.js │ │ │ │ ├── src │ │ │ │ │ └── App.js │ │ │ │ ├── index.html │ │ │ │ └── screenshot.sh │ │ │ └── img │ │ │ │ ├── add-todo-item.png │ │ │ │ └── prevent-event.png │ │ ├── update-delete │ │ │ ├── add-checkbox │ │ │ │ ├── index.js │ │ │ │ ├── src │ │ │ │ │ └── model │ │ │ │ │ │ ├── TodoItemModel.example.js │ │ │ │ │ │ ├── TodoItemModel.js │ │ │ │ │ │ ├── TodoListModel.example.js │ │ │ │ │ │ └── TodoListModel.js │ │ │ │ └── index.html │ │ │ ├── delete-feature │ │ │ │ ├── index.js │ │ │ │ ├── src │ │ │ │ │ └── model │ │ │ │ │ │ ├── TodoItemModel.example.js │ │ │ │ │ │ ├── TodoItemModel.js │ │ │ │ │ │ └── TodoListModel.example.js │ │ │ │ └── index.html │ │ │ ├── update-feature │ │ │ │ ├── index.js │ │ │ │ ├── src │ │ │ │ │ └── model │ │ │ │ │ │ ├── TodoItemModel.example.js │ │ │ │ │ │ ├── TodoItemModel.js │ │ │ │ │ │ └── TodoListModel.example.js │ │ │ │ └── index.html │ │ │ ├── img │ │ │ │ └── input-checkbox.png │ │ │ └── input-checkbox │ │ │ │ └── index.html │ │ ├── SCREENSHOT.md │ │ ├── cypress │ │ │ ├── fixtures │ │ │ │ └── example.json │ │ │ ├── helper │ │ │ │ ├── todo-helper.js │ │ │ │ └── visit-with-console.js │ │ │ ├── integration │ │ │ │ ├── entrypoint │ │ │ │ │ ├── first-entry │ │ │ │ │ │ └── first-entry-spec.js │ │ │ │ │ └── module-entry │ │ │ │ │ │ └── module-entry-spec.js │ │ │ │ ├── form-event │ │ │ │ │ ├── prevent-event │ │ │ │ │ │ └── prevent-event-spec.js │ │ │ │ │ └── add-todo-item │ │ │ │ │ │ └── add-todo-item-spec.js │ │ │ │ ├── app-structure │ │ │ │ │ └── todo-html │ │ │ │ │ │ └── todo-html-spec.js │ │ │ │ ├── update-delete │ │ │ │ │ └── add-checkbox │ │ │ │ │ │ └── add-checkbox.js │ │ │ │ └── event-model │ │ │ │ │ └── event-emitter │ │ │ │ │ └── event-emitter-spec.js │ │ │ ├── support │ │ │ │ ├── index.js │ │ │ │ └── commands.js │ │ │ └── plugins │ │ │ │ └── index.js │ │ ├── screenshot-auto.sh │ │ ├── screenshot-manual.sh │ │ └── package.json │ └── README.md ├── landing │ └── img │ │ ├── cover.jpg │ │ ├── js-primer.png │ │ ├── cover-optimized.jpg │ │ └── repo-actions-watch.png ├── gitbook │ ├── icons │ │ ├── favicon.ico │ │ ├── icon-72x72.png │ │ ├── icon-96x96.png │ │ ├── amazon-icon.png │ │ ├── icon-128x128.png │ │ ├── icon-144x144.png │ │ ├── icon-152x152.png │ │ ├── icon-192x192.png │ │ ├── icon-384x384.png │ │ └── icon-512x512.png │ └── sponsors │ │ └── being-ish.png ├── index.md ├── 404.html ├── cheetsheet │ └── index.html ├── outro │ └── README.md ├── intro │ ├── authors │ │ └── README.md │ └── goal │ │ └── README.md └── manifest.json ├── .mocharc.json ├── .netlify └── state.json ├── .prettierignore ├── netlify.toml ├── .github ├── ISSUE_TEMPLATE │ ├── other.md │ ├── question.md │ ├── bug_report.md │ └── feature_request.md ├── FUNDING.yml └── workflows │ ├── deploy.yml │ ├── link-check.yml │ └── calibreapp-image-manual.yml ├── resources ├── js-primer.sketch ├── fonts │ └── ipaexm00301 │ │ └── ipaexm.ttf └── gitbook-plugin-include-codeblock.hbs ├── meetings ├── 2016-01-29 │ └── azu │ │ ├── startup-book.pdf │ │ └── how-to-learn-ruby.png ├── 2017-11-02 │ └── img │ │ └── lts-schedule.png ├── README.md └── 2015-12-17 │ └── README.md ├── .bookignore ├── tools ├── applescript │ ├── modules │ │ ├── wait.ts │ │ └── keyboard-util.ts │ ├── src │ │ ├── screenshot-only.ts │ │ ├── launch-firefox.ts │ │ └── screenshot.ts │ └── tsconfig.json ├── textstat.cjs ├── gitbook │ └── copy-favicon.cjs ├── update-summary.sh └── update-package-lock.mjs ├── .mcp.json ├── .vscode └── mcp.json ├── textlint ├── textlint-rule-inline-code-denylist │ ├── package.json │ └── index.js ├── textlint-rule-no-use-column │ └── package.json ├── textlint-rule-static-method-syntax │ ├── README.md │ ├── package.json │ └── index.js └── textlint-rule-no-use-prototype-hash │ └── package.json ├── .editorconfig ├── LICENSE-MIT └── test └── example-es-test.js /.node-version: -------------------------------------------------------------------------------- 1 | v22.18.0 2 | -------------------------------------------------------------------------------- /source/CNAME: -------------------------------------------------------------------------------- 1 | jsprimer.net -------------------------------------------------------------------------------- /source/basic/async/example/async-await.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /source/use-case/ajaxapp/test/index-test.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.mocharc.json: -------------------------------------------------------------------------------- 1 | { 2 | "timeout": "10000" 3 | } 4 | -------------------------------------------------------------------------------- /source/basic/read-eval-print/src/example/index1.js: -------------------------------------------------------------------------------- 1 | 1; -------------------------------------------------------------------------------- /source/use-case/nodecli/read-file/src/sample.md: -------------------------------------------------------------------------------- 1 | # sample -------------------------------------------------------------------------------- /source/basic/data-type/src/var-null-invalid.js: -------------------------------------------------------------------------------- 1 | let null; // => SyntaxError 2 | -------------------------------------------------------------------------------- /source/basic/module/src/math-utils.js: -------------------------------------------------------------------------------- 1 | export const add = (a, b) => a + b; 2 | -------------------------------------------------------------------------------- /source/basic/module/test/export-4.js: -------------------------------------------------------------------------------- 1 | import foo from "../src/export-4.js"; 2 | -------------------------------------------------------------------------------- /source/use-case/nodecli/helloworld/src/main.js: -------------------------------------------------------------------------------- 1 | console.log("Hello World!"); -------------------------------------------------------------------------------- /source/use-case/nodecli/test/snapshots/helloworld_src_main.js.txt: -------------------------------------------------------------------------------- 1 | Hello World! -------------------------------------------------------------------------------- /source/use-case/nodecli/test/snapshots/read-file_src_main-2.js.txt: -------------------------------------------------------------------------------- 1 | # sample -------------------------------------------------------------------------------- /source/use-case/nodecli/test/snapshots/read-file_src_main-3.js.txt: -------------------------------------------------------------------------------- 1 | # sample -------------------------------------------------------------------------------- /source/use-case/setup-local-env/src/index.js: -------------------------------------------------------------------------------- 1 | console.log("index.js: loaded"); -------------------------------------------------------------------------------- /.netlify/state.json: -------------------------------------------------------------------------------- 1 | { 2 | "siteId": "08c7b07a-086c-4261-befe-6f0ffeca823a" 3 | } -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | source/use-case/nodecli/refactor-and-unittest/src/test/fixtures 2 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | publish = "_book" 3 | command = "npm run build" 4 | -------------------------------------------------------------------------------- /source/basic/module/src/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "John", 3 | "age": 30 4 | } 5 | -------------------------------------------------------------------------------- /source/basic/read-eval-print/src/console-example.js: -------------------------------------------------------------------------------- 1 | console.log(1); // => 1 2 | -------------------------------------------------------------------------------- /source/basic/read-eval-print/src/invalid/reference-error.js: -------------------------------------------------------------------------------- 1 | cosntという変数名 aという変数名 = 1; -------------------------------------------------------------------------------- /source/basic/read-eval-print/src/invalid/syntax-error-typo/index.js: -------------------------------------------------------------------------------- 1 | cosnt a = 1; -------------------------------------------------------------------------------- /source/use-case/ajaxapp/entrypoint/example/index.js: -------------------------------------------------------------------------------- 1 | console.log("index.js: loaded"); -------------------------------------------------------------------------------- /source/use-case/nodecli/test/snapshots/argument-parse_src_main-2.js.txt: -------------------------------------------------------------------------------- 1 | sample.md -------------------------------------------------------------------------------- /source/basic/module/src/my-module-2.js: -------------------------------------------------------------------------------- 1 | export default { 2 | baz: "baz" 3 | }; 4 | -------------------------------------------------------------------------------- /source/basic/module/src/side-effects.js: -------------------------------------------------------------------------------- 1 | // グローバル変数を操作する(副作用) 2 | window.foo = "foo"; 3 | -------------------------------------------------------------------------------- /source/basic/module/test/export-2.js: -------------------------------------------------------------------------------- 1 | import { foo, bar } from "../src/export-2.js"; 2 | -------------------------------------------------------------------------------- /source/basic/module/test/export-5.js: -------------------------------------------------------------------------------- 1 | import bar, { foo } from "../src/export-5.js"; 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/other.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: その他 3 | about: その他のIssueを立てたい場合はこちら 4 | --- 5 | -------------------------------------------------------------------------------- /source/basic/module/test/export-1.js: -------------------------------------------------------------------------------- 1 | import { foo, bar } from "../src/export-1.js.js"; 2 | -------------------------------------------------------------------------------- /source/use-case/todoapp/cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseUrl": "http://localhost:3000/" 3 | } 4 | -------------------------------------------------------------------------------- /source/use-case/todoapp/entrypoint/first-entry/index.js: -------------------------------------------------------------------------------- 1 | console.log("index.js: loaded"); 2 | -------------------------------------------------------------------------------- /source/basic/module/src/my-module-1.js: -------------------------------------------------------------------------------- 1 | export const foo = "foo"; 2 | export function bar() { } 3 | -------------------------------------------------------------------------------- /source/basic/read-eval-print/src/console-expression-example.js: -------------------------------------------------------------------------------- 1 | console.log(1 + 1); // => 2 2 | -------------------------------------------------------------------------------- /source/use-case/nodecli/test/snapshots/read-file_src_main-1.js.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /source/basic/module/test/export-3.js: -------------------------------------------------------------------------------- 1 | import { foo, bar, fn, ClassName } from "../src/export-3.js"; 2 | -------------------------------------------------------------------------------- /source/basic/module/src/named-export-declare.js: -------------------------------------------------------------------------------- 1 | // 宣言と同時に名前つきエクスポートする 2 | export function bar() { }; 3 | -------------------------------------------------------------------------------- /source/use-case/nodecli/refactor-and-unittest/src/test/.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | insert_final_newline = false -------------------------------------------------------------------------------- /resources/js-primer.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidwango/js-primer/HEAD/resources/js-primer.sketch -------------------------------------------------------------------------------- /source/basic/error-try-catch/src/error.js: -------------------------------------------------------------------------------- 1 | function fn() { 2 | // 存在しない変数を参照する 3 | x++; 4 | } 5 | fn(); -------------------------------------------------------------------------------- /source/landing/img/cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidwango/js-primer/HEAD/source/landing/img/cover.jpg -------------------------------------------------------------------------------- /source/use-case/nodecli/argument-parse/src/main-1.js: -------------------------------------------------------------------------------- 1 | // コンソールにコマンドライン引数を出力する 2 | console.log(process.argv); 3 | -------------------------------------------------------------------------------- /source/basic/module/src/default-export.js: -------------------------------------------------------------------------------- 1 | const foo = "foo"; 2 | // foo変数の値をデフォルトエクスポートする 3 | export default foo; 4 | -------------------------------------------------------------------------------- /source/basic/module/src/import-side-effects.js: -------------------------------------------------------------------------------- 1 | // ./side-effects.jsのグローバルコードが実行される 2 | import "./side-effects.js"; 3 | -------------------------------------------------------------------------------- /source/basic/module/src/named-export.js: -------------------------------------------------------------------------------- 1 | const foo = "foo"; 2 | // 宣言済みのオブジェクトを名前つきエクスポートする 3 | export { foo }; 4 | -------------------------------------------------------------------------------- /source/basic/object/src/prop-invalid.js: -------------------------------------------------------------------------------- 1 | const object = { 2 | // キー: 値 3 | my-prop: "value" // NG 4 | }; 5 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: azu 2 | open_collective: jsprimer 3 | custom: ["https://www.amazon.co.jp/dp/4048931105/"] 4 | -------------------------------------------------------------------------------- /source/basic/read-eval-print/src/console-variable-example.js: -------------------------------------------------------------------------------- 1 | const total = 42 + 42; 2 | console.log(total); // => 84 3 | -------------------------------------------------------------------------------- /source/basic/read-eval-print/src/example/index.js: -------------------------------------------------------------------------------- 1 | const book = "js-primer"; 2 | console.log(book); // => "js-primer" 3 | -------------------------------------------------------------------------------- /source/gitbook/icons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidwango/js-primer/HEAD/source/gitbook/icons/favicon.ico -------------------------------------------------------------------------------- /source/landing/img/js-primer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidwango/js-primer/HEAD/source/landing/img/js-primer.png -------------------------------------------------------------------------------- /source/use-case/todoapp/app-structure/todo-html/index.js: -------------------------------------------------------------------------------- 1 | import { App } from "./src/App.js"; 2 | const app = new App(); 3 | -------------------------------------------------------------------------------- /source/use-case/todoapp/entrypoint/module-entry/index.js: -------------------------------------------------------------------------------- 1 | import { App } from "./src/App.js"; 2 | const app = new App(); 3 | -------------------------------------------------------------------------------- /source/basic/function-scope/src/identifier-duplicated-invalid.js: -------------------------------------------------------------------------------- 1 | // スコープ内に同じ"a"を定義すると SyntaxError となる 2 | let a; 3 | let a; 4 | -------------------------------------------------------------------------------- /source/basic/read-eval-print/src/invalid/syntax-error/index.js: -------------------------------------------------------------------------------- 1 | console.log(1; // => SyntaxError: missing ) after argument list -------------------------------------------------------------------------------- /source/gitbook/icons/icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidwango/js-primer/HEAD/source/gitbook/icons/icon-72x72.png -------------------------------------------------------------------------------- /source/gitbook/icons/icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidwango/js-primer/HEAD/source/gitbook/icons/icon-96x96.png -------------------------------------------------------------------------------- /source/use-case/todoapp/final/final/index.js: -------------------------------------------------------------------------------- 1 | import { App } from "./src/App.js"; 2 | const app = new App(); 3 | app.mount(); 4 | -------------------------------------------------------------------------------- /resources/fonts/ipaexm00301/ipaexm.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidwango/js-primer/HEAD/resources/fonts/ipaexm00301/ipaexm.ttf -------------------------------------------------------------------------------- /source/basic/read-eval-print/src/runtime-error/index.js: -------------------------------------------------------------------------------- 1 | const value = "値"; 2 | console.log(x); // => ReferenceError: x is not defined -------------------------------------------------------------------------------- /source/gitbook/icons/amazon-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidwango/js-primer/HEAD/source/gitbook/icons/amazon-icon.png -------------------------------------------------------------------------------- /source/gitbook/icons/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidwango/js-primer/HEAD/source/gitbook/icons/icon-128x128.png -------------------------------------------------------------------------------- /source/gitbook/icons/icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidwango/js-primer/HEAD/source/gitbook/icons/icon-144x144.png -------------------------------------------------------------------------------- /source/gitbook/icons/icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidwango/js-primer/HEAD/source/gitbook/icons/icon-152x152.png -------------------------------------------------------------------------------- /source/gitbook/icons/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidwango/js-primer/HEAD/source/gitbook/icons/icon-192x192.png -------------------------------------------------------------------------------- /source/gitbook/icons/icon-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidwango/js-primer/HEAD/source/gitbook/icons/icon-384x384.png -------------------------------------------------------------------------------- /source/gitbook/icons/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidwango/js-primer/HEAD/source/gitbook/icons/icon-512x512.png -------------------------------------------------------------------------------- /source/gitbook/sponsors/being-ish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidwango/js-primer/HEAD/source/gitbook/sponsors/being-ish.png -------------------------------------------------------------------------------- /source/landing/img/cover-optimized.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidwango/js-primer/HEAD/source/landing/img/cover-optimized.jpg -------------------------------------------------------------------------------- /meetings/2016-01-29/azu/startup-book.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidwango/js-primer/HEAD/meetings/2016-01-29/azu/startup-book.pdf -------------------------------------------------------------------------------- /meetings/2017-11-02/img/lts-schedule.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidwango/js-primer/HEAD/meetings/2017-11-02/img/lts-schedule.png -------------------------------------------------------------------------------- /meetings/README.md: -------------------------------------------------------------------------------- 1 | # Meeting Notes 2 | 3 | この書籍を開発について行ったミーティングの議事録です。 4 | 5 | ミーティングでは事前にアジェンダとなるIssueを元に、進捗や議題となっていることについて話あっています。 6 | -------------------------------------------------------------------------------- /source/basic/async/img/promise-chain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidwango/js-primer/HEAD/source/basic/async/img/promise-chain.png -------------------------------------------------------------------------------- /source/basic/loop/src/do-while/do-while-example.js: -------------------------------------------------------------------------------- 1 | const x = 1000; 2 | do { 3 | console.log(x); // => 1000 4 | } while (x < 10); 5 | -------------------------------------------------------------------------------- /source/basic/module/src/default-export-alias.js: -------------------------------------------------------------------------------- 1 | const foo = "foo"; 2 | // foo変数の値をデフォルトエクスポートする 3 | export { foo as default }; 4 | 5 | -------------------------------------------------------------------------------- /source/use-case/nodecli/md-to-html/src/sample.md: -------------------------------------------------------------------------------- 1 | # サンプルファイル 2 | 3 | これはサンプルです。 4 | https://jsprimer.net/ 5 | 6 | - サンプル1 7 | - サンプル2 8 | -------------------------------------------------------------------------------- /source/use-case/todoapp/final/create-view/index.js: -------------------------------------------------------------------------------- 1 | import { App } from "./src/App.js"; 2 | const app = new App(); 3 | app.mount(); 4 | -------------------------------------------------------------------------------- /.bookignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package.json 3 | theme 4 | 5 | # project 6 | meetings 7 | OUTLINE.md 8 | .travis 9 | .bookignore 10 | -------------------------------------------------------------------------------- /source/basic/error-try-catch/img/error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidwango/js-primer/HEAD/source/basic/error-try-catch/img/error.png -------------------------------------------------------------------------------- /source/basic/module/src/default-export-variables-invalid.js: -------------------------------------------------------------------------------- 1 | // 変数宣言と同時にデフォルトエクスポートはできない 2 | export default const foo = "foo", bar = "bar"; 3 | -------------------------------------------------------------------------------- /source/basic/module/src/my-module.js: -------------------------------------------------------------------------------- 1 | export const foo = "foo"; 2 | export function bar() { } 3 | export default { 4 | baz: "baz" 5 | }; 6 | -------------------------------------------------------------------------------- /source/basic/string/src/multiline-invalid.js: -------------------------------------------------------------------------------- 1 | // JavaScriptエンジンが構文として解釈できないため、SyntaxErrorとなる 2 | const invalidString = "1行目 3 | 2行目 4 | 3行目"; -------------------------------------------------------------------------------- /source/landing/img/repo-actions-watch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidwango/js-primer/HEAD/source/landing/img/repo-actions-watch.png -------------------------------------------------------------------------------- /source/use-case/ajaxapp/http/img/fig-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidwango/js-primer/HEAD/source/use-case/ajaxapp/http/img/fig-1.png -------------------------------------------------------------------------------- /source/use-case/ajaxapp/http/img/fig-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidwango/js-primer/HEAD/source/use-case/ajaxapp/http/img/fig-2.png -------------------------------------------------------------------------------- /source/use-case/todoapp/event-model/event-emitter/index.js: -------------------------------------------------------------------------------- 1 | import { App } from "./src/App.js"; 2 | const app = new App(); 3 | app.mount(); 4 | -------------------------------------------------------------------------------- /source/use-case/todoapp/form-event/add-todo-item/index.js: -------------------------------------------------------------------------------- 1 | import { App } from "./src/App.js"; 2 | const app = new App(); 3 | app.mount(); 4 | -------------------------------------------------------------------------------- /source/use-case/todoapp/form-event/prevent-event/index.js: -------------------------------------------------------------------------------- 1 | import { App } from "./src/App.js"; 2 | const app = new App(); 3 | app.mount(); 4 | -------------------------------------------------------------------------------- /source/use-case/todoapp/update-delete/add-checkbox/index.js: -------------------------------------------------------------------------------- 1 | import { App } from "./src/App.js"; 2 | const app = new App(); 3 | app.mount(); 4 | -------------------------------------------------------------------------------- /meetings/2016-01-29/azu/how-to-learn-ruby.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidwango/js-primer/HEAD/meetings/2016-01-29/azu/how-to-learn-ruby.png -------------------------------------------------------------------------------- /source/basic/async/img/promise-chain.graffle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidwango/js-primer/HEAD/source/basic/async/img/promise-chain.graffle -------------------------------------------------------------------------------- /source/use-case/ajaxapp/display/img/fig-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidwango/js-primer/HEAD/source/use-case/ajaxapp/display/img/fig-1.png -------------------------------------------------------------------------------- /source/use-case/ajaxapp/promise/img/fig-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidwango/js-primer/HEAD/source/use-case/ajaxapp/promise/img/fig-1.png -------------------------------------------------------------------------------- /source/use-case/setup-local-env/img/index.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidwango/js-primer/HEAD/source/use-case/setup-local-env/img/index.png -------------------------------------------------------------------------------- /source/use-case/todoapp/update-delete/delete-feature/index.js: -------------------------------------------------------------------------------- 1 | import { App } from "./src/App.js"; 2 | const app = new App(); 3 | app.mount(); 4 | -------------------------------------------------------------------------------- /source/use-case/todoapp/update-delete/update-feature/index.js: -------------------------------------------------------------------------------- 1 | import { App } from "./src/App.js"; 2 | const app = new App(); 3 | app.mount(); 4 | -------------------------------------------------------------------------------- /source/basic/array/img/array.prototype.slice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidwango/js-primer/HEAD/source/basic/array/img/array.prototype.slice.png -------------------------------------------------------------------------------- /source/basic/async/img/single-thread-tasks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidwango/js-primer/HEAD/source/basic/async/img/single-thread-tasks.png -------------------------------------------------------------------------------- /source/basic/async/img/then-rejected-promise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidwango/js-primer/HEAD/source/basic/async/img/then-rejected-promise.png -------------------------------------------------------------------------------- /source/basic/error-try-catch/img/error-cause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidwango/js-primer/HEAD/source/basic/error-try-catch/img/error-cause.png -------------------------------------------------------------------------------- /source/basic/error-try-catch/src/console/index.js: -------------------------------------------------------------------------------- 1 | function fn() { 2 | console.log("メッセージ"); 3 | console.error("エラーメッセージ"); 4 | } 5 | 6 | fn(); -------------------------------------------------------------------------------- /source/basic/function-declaration/src/arguments.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | function a(...test) { 4 | console.log(arguments); 5 | } 6 | 7 | a(1); 8 | -------------------------------------------------------------------------------- /source/basic/loop/public/index.js: -------------------------------------------------------------------------------- 1 | let i = 1; 2 | // 条件式が常にtrueになるため、無限ループする 3 | while (i > 0) { 4 | console.log(`${i}回目のループ`); 5 | i += 1; 6 | } -------------------------------------------------------------------------------- /source/basic/read-eval-print/img/web-console.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidwango/js-primer/HEAD/source/basic/read-eval-print/img/web-console.png -------------------------------------------------------------------------------- /source/use-case/ajaxapp/entrypoint/img/fig-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidwango/js-primer/HEAD/source/use-case/ajaxapp/entrypoint/img/fig-1.png -------------------------------------------------------------------------------- /source/use-case/nodecli/refactor-and-unittest/src/example/greet-main.js: -------------------------------------------------------------------------------- 1 | import { greet } from "./greet.js"; 2 | greet("World"); // => "Hello World!" 3 | -------------------------------------------------------------------------------- /source/use-case/nodecli/refactor-and-unittest/src/sample.md: -------------------------------------------------------------------------------- 1 | # サンプルファイル 2 | 3 | これはサンプルです。 4 | https://jsprimer.net/ 5 | 6 | - サンプル1 7 | - サンプル2 8 | -------------------------------------------------------------------------------- /source/basic/data-type/src/multiple-line-string-invalid.js: -------------------------------------------------------------------------------- 1 | "複数行の 2 | 文字列を 3 | 入れたい"; // => SyntaxError: "" string literal contains an unescaped line break 4 | -------------------------------------------------------------------------------- /source/basic/error-try-catch/img/console.error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidwango/js-primer/HEAD/source/basic/error-try-catch/img/console.error.png -------------------------------------------------------------------------------- /source/basic/module/src/named-export-alias.js: -------------------------------------------------------------------------------- 1 | const internalFoo = "foo"; 2 | // internalFoo変数をfooとして名前つきエクスポートする 3 | export { internalFoo as foo }; 4 | 5 | -------------------------------------------------------------------------------- /source/basic/read-eval-print/img/syntax-error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidwango/js-primer/HEAD/source/basic/read-eval-print/img/syntax-error.png -------------------------------------------------------------------------------- /source/use-case/nodecli/refactor-and-unittest/src/example/greet.js: -------------------------------------------------------------------------------- 1 | // greet.js 2 | export function greet(name) { 3 | return `Hello ${name}!`; 4 | }; 5 | -------------------------------------------------------------------------------- /source/basic/async/img/async-single-thread-tasks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidwango/js-primer/HEAD/source/basic/async/img/async-single-thread-tasks.png -------------------------------------------------------------------------------- /source/basic/condition/src/if/if-example.js: -------------------------------------------------------------------------------- 1 | // 現在の西暦を`year`に代入 2 | const year = new Date().getFullYear(); 3 | if (year > 2015) { 4 | console.log(year); 5 | } -------------------------------------------------------------------------------- /source/basic/variables/src/const-without-assign-invalid.js: -------------------------------------------------------------------------------- 1 | const bookTitle; // SyntaxError: missing = in const declaration 2 | bookTitle = "JavaScript Primer"; 3 | -------------------------------------------------------------------------------- /source/use-case/nodecli/refactor-and-unittest/src/test/fixtures/sample.md: -------------------------------------------------------------------------------- 1 | # サンプルファイル 2 | 3 | これはサンプルです。 4 | https://jsprimer.net/ 5 | 6 | - サンプル1 7 | - サンプル2 8 | -------------------------------------------------------------------------------- /source/basic/introduction/img/javascript-ecmascript.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidwango/js-primer/HEAD/source/basic/introduction/img/javascript-ecmascript.png -------------------------------------------------------------------------------- /source/basic/object/src/prop-dot-invalid.js: -------------------------------------------------------------------------------- 1 | obj.key; // OK 2 | // プロパティ名が数字から始まる識別子は利用できない 3 | obj.123; // NG 4 | // プロパティ名にハイフンを含む識別子は利用できない 5 | obj.my-prop; // NG 6 | -------------------------------------------------------------------------------- /source/basic/prototype-object/img/object-prototype.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidwango/js-primer/HEAD/source/basic/prototype-object/img/object-prototype.png -------------------------------------------------------------------------------- /source/use-case/todoapp/app-structure/img/todo-html.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidwango/js-primer/HEAD/source/use-case/todoapp/app-structure/img/todo-html.png -------------------------------------------------------------------------------- /source/use-case/todoapp/entrypoint/img/first-entry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidwango/js-primer/HEAD/source/use-case/todoapp/entrypoint/img/first-entry.png -------------------------------------------------------------------------------- /source/basic/data-type/src/octal-legacy-literal-invalid.js: -------------------------------------------------------------------------------- 1 | // 非推奨な8進数の書き方 2 | // strict modeは例外が発生 3 | console.log(0644); // => 420 4 | console.log(0777); // => 511 5 | -------------------------------------------------------------------------------- /source/basic/module/src/default-import.js: -------------------------------------------------------------------------------- 1 | // デフォルトエクスポートをmyModuleとしてインポートする 2 | import myModule from "./my-module.js"; 3 | console.log(myModule); // => { baz: "baz" } 4 | -------------------------------------------------------------------------------- /source/basic/prototype-object/img/object-prototype.graffle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidwango/js-primer/HEAD/source/basic/prototype-object/img/object-prototype.graffle -------------------------------------------------------------------------------- /source/use-case/todoapp/form-event/img/add-todo-item.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidwango/js-primer/HEAD/source/use-case/todoapp/form-event/img/add-todo-item.png -------------------------------------------------------------------------------- /source/use-case/todoapp/form-event/img/prevent-event.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidwango/js-primer/HEAD/source/use-case/todoapp/form-event/img/prevent-event.png -------------------------------------------------------------------------------- /source/basic/async/img/block--async-single-thread-tasks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidwango/js-primer/HEAD/source/basic/async/img/block--async-single-thread-tasks.png -------------------------------------------------------------------------------- /source/basic/loop/src/for-of/for-of-array-example.js: -------------------------------------------------------------------------------- 1 | const array = [1, 2, 3]; 2 | for (const value of array) { 3 | console.log(value); 4 | } 5 | // 1 6 | // 2 7 | // 3 8 | -------------------------------------------------------------------------------- /source/basic/loop/src/for-of/for-of-string-example.js: -------------------------------------------------------------------------------- 1 | const str = "𠮷野家"; 2 | for (const value of str) { 3 | console.log(value); 4 | } 5 | // "𠮷" 6 | // "野" 7 | // "家" 8 | -------------------------------------------------------------------------------- /source/basic/string-unicode/img/codeunit-codepoint-table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidwango/js-primer/HEAD/source/basic/string-unicode/img/codeunit-codepoint-table.png -------------------------------------------------------------------------------- /source/use-case/todoapp/update-delete/img/input-checkbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidwango/js-primer/HEAD/source/use-case/todoapp/update-delete/img/input-checkbox.png -------------------------------------------------------------------------------- /tools/applescript/modules/wait.ts: -------------------------------------------------------------------------------- 1 | export const wait = (waitMs: number) => { 2 | return new Promise(resolve => { 3 | setTimeout(resolve, waitMs); 4 | }) 5 | }; 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: 書籍に関する質問をする 4 | 5 | --- 6 | 7 | ## 該当ページ 8 | 9 | - [ ] 質問があるページのURLを入れてください 10 | 11 | ## 質問内容 12 | -------------------------------------------------------------------------------- /source/basic/error-try-catch/src/error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /source/basic/function-this/src/this-is-readonly-invalid.js: -------------------------------------------------------------------------------- 1 | // thisはキーワードであるため、ユーザーは`this`という名前の変数を定義できない 2 | const this = "thisは読み取り専用"; // => SyntaxError: Unexpected token this 3 | -------------------------------------------------------------------------------- /source/basic/statement-expression/src/statement-not-expression-invalid.js: -------------------------------------------------------------------------------- 1 | // 構文として間違っているため、SyntaxErrorが発生する 2 | const forIsNotExpression = if (true) { /* ifは文であるため式にはなれない */ } 3 | -------------------------------------------------------------------------------- /source/basic/implicit-coercion/img/JavaScript-Equality-Table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidwango/js-primer/HEAD/source/basic/implicit-coercion/img/JavaScript-Equality-Table.png -------------------------------------------------------------------------------- /source/basic/module/src/named-import-alias.js: -------------------------------------------------------------------------------- 1 | // fooとして名前つきエクスポートされた変数をmyFooとしてインポートする 2 | import { foo as myFoo } from "./named-export-alias.js"; 3 | console.log(myFoo); // => "foo" 4 | -------------------------------------------------------------------------------- /source/basic/variables/src/const-do-not-assign-invalid.js: -------------------------------------------------------------------------------- 1 | const bookTitle = "JavaScript Primer"; 2 | bookTitle = "新しいタイトル"; // => TypeError: invalid assignment to const 'bookTitle' 3 | -------------------------------------------------------------------------------- /source/basic/variables/src/let-duplicated-define-invalid.js: -------------------------------------------------------------------------------- 1 | // "x"という変数名で変数を定義する 2 | let x; 3 | // 同じ名前の変数"x"を定義するとSyntaxErrorとなる 4 | let x; // => SyntaxError: redeclaration of let x 5 | -------------------------------------------------------------------------------- /source/basic/module/src/default-import-alias.js: -------------------------------------------------------------------------------- 1 | // デフォルトエクスポートをmyModuleとしてインポートする 2 | import { default as myModule } from "./my-module.js"; 3 | console.log(myModule); // => { baz: "baz" } 4 | -------------------------------------------------------------------------------- /source/basic/module/src/import-attributes.js: -------------------------------------------------------------------------------- 1 | import jsonData from "./data.json" with { type: "json" }; 2 | console.log(jsonData.name); // => "John" 3 | console.log(jsonData.age); // => 30 4 | -------------------------------------------------------------------------------- /source/basic/string-unicode/img/emoji-codeunit-codepoint-table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidwango/js-primer/HEAD/source/basic/string-unicode/img/emoji-codeunit-codepoint-table.png -------------------------------------------------------------------------------- /source/basic/string-unicode/img/extenal-code-and-internal-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidwango/js-primer/HEAD/source/basic/string-unicode/img/extenal-code-and-internal-code.png -------------------------------------------------------------------------------- /source/use-case/nodecli/test/snapshots/md-to-html_src_main.js.txt: -------------------------------------------------------------------------------- 1 |

サンプルファイル

2 |

これはサンプルです。 3 | https://jsprimer.net/

4 | 8 | -------------------------------------------------------------------------------- /source/basic/operator/img/0000_0000_0000_0000_0000_0000_0000_0001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidwango/js-primer/HEAD/source/basic/operator/img/0000_0000_0000_0000_0000_0000_0000_0001.png -------------------------------------------------------------------------------- /source/basic/operator/img/1111_1111_1111_1111_1111_1111_1111_1111.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidwango/js-primer/HEAD/source/basic/operator/img/1111_1111_1111_1111_1111_1111_1111_1111.png -------------------------------------------------------------------------------- /source/use-case/nodecli/test/snapshots/md-to-html_src_main-2.js.txt: -------------------------------------------------------------------------------- 1 |

サンプルファイル

2 |

これはサンプルです。 3 | https://jsprimer.net/

4 | 8 | -------------------------------------------------------------------------------- /source/use-case/nodecli/test/snapshots/md-to-html_src_main-3.js.txt: -------------------------------------------------------------------------------- 1 |

サンプルファイル

2 |

これはサンプルです。 3 | https://jsprimer.net/

4 | 8 | -------------------------------------------------------------------------------- /source/basic/loop/src/break/some-even-example.js: -------------------------------------------------------------------------------- 1 | function isEven(num) { 2 | return num % 2 === 0; 3 | } 4 | const numbers = [1, 5, 10, 15, 20]; 5 | console.log(numbers.some(isEven)); // => true 6 | -------------------------------------------------------------------------------- /source/basic/module/src/named-import.js: -------------------------------------------------------------------------------- 1 | // 名前つきエクスポートされたfooとbarをインポートする 2 | import { foo, bar } from "./my-module.js"; 3 | console.log(foo); // => "foo" 4 | console.log(bar); // => function bar() 5 | -------------------------------------------------------------------------------- /source/use-case/nodecli/test/snapshots/refactor-and-unittest_src_main.js.txt: -------------------------------------------------------------------------------- 1 |

サンプルファイル

2 |

これはサンプルです。 3 | https://jsprimer.net/

4 | 8 | -------------------------------------------------------------------------------- /source/use-case/todoapp/app-structure/todo-html/src/App.js: -------------------------------------------------------------------------------- 1 | console.log("App.js: loaded"); 2 | export class App { 3 | constructor() { 4 | console.log("App initialized"); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /source/use-case/todoapp/entrypoint/first-entry/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todoapp", 3 | "private": true, 4 | "scripts": { 5 | "start": "npx --yes @js-primer/local-server" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /source/use-case/todoapp/entrypoint/module-entry/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todoapp", 3 | "private": true, 4 | "scripts": { 5 | "start": "npx --yes @js-primer/local-server" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /source/use-case/todoapp/entrypoint/module-entry/src/App.js: -------------------------------------------------------------------------------- 1 | console.log("App.js: loaded"); 2 | export class App { 3 | constructor() { 4 | console.log("App initialized"); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /source/basic/loop/src/while/while-add-example.js: -------------------------------------------------------------------------------- 1 | let x = 0; 2 | console.log(`ループ開始前のxの値: ${x}`); 3 | while (x < 10) { 4 | console.log(x); 5 | x += 1; 6 | } 7 | console.log(`ループ終了後のxの値: ${x}`); 8 | -------------------------------------------------------------------------------- /source/use-case/nodecli/refactor-and-unittest/src/test/fixtures/expected.html: -------------------------------------------------------------------------------- 1 |

サンプルファイル

2 |

これはサンプルです。 3 | https://jsprimer.net/

4 | 8 | -------------------------------------------------------------------------------- /source/use-case/nodecli/test/snapshots/refactor-and-unittest_src_main-last.js.txt: -------------------------------------------------------------------------------- 1 |

サンプルファイル

2 |

これはサンプルです。 3 | https://jsprimer.net/

4 | 8 | -------------------------------------------------------------------------------- /source/basic/loop/src/continue/filter-even-example.js: -------------------------------------------------------------------------------- 1 | function isEven(num) { 2 | return num % 2 === 0; 3 | } 4 | 5 | const array = [1, 5, 10, 15, 20]; 6 | console.log(array.filter(isEven)); // => [10, 20] 7 | -------------------------------------------------------------------------------- /source/use-case/todoapp/SCREENSHOT.md: -------------------------------------------------------------------------------- 1 | # スクリーンショットの更新 2 | 3 | TODOのスクリーンショットの更新フロー 4 | 5 | ```bash 6 | # 手動の対応が必要なもの 7 | screenshot-manual.sh 8 | # 自動でスクリーンショットをとる画面 9 | screenshot-auto.sh 10 | ``` 11 | -------------------------------------------------------------------------------- /source/basic/object/src/freeze-property-invalid.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const object = Object.freeze({ key: "value" }); 3 | // freezeしたオブジェクトにプロパティを追加や変更できない 4 | object.key = "value"; // => TypeError: "key" is read-only -------------------------------------------------------------------------------- /source/basic/read-eval-print/src/example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Example 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /source/basic/array/src/const-empty-array-invalid.js: -------------------------------------------------------------------------------- 1 | const array = [1, 2, 3]; 2 | console.log(array.length); // => 3 3 | // `const`で宣言された変数には再代入できない 4 | array = []; // TypeError: invalid assignment to const `array' が発生 5 | -------------------------------------------------------------------------------- /source/basic/read-eval-print/src/runtime-error/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Example 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /source/use-case/todoapp/cypress/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io", 4 | "body": "Fixtures are a great way to mock data for responses to routes" 5 | } -------------------------------------------------------------------------------- /source/basic/loop/src/for/sum-reduce-example.js: -------------------------------------------------------------------------------- 1 | function sum(numbers) { 2 | return numbers.reduce((total, num) => { 3 | return total + num; 4 | }, 0); // 初期値が0 5 | } 6 | 7 | sum([1, 2, 3, 4, 5]); // => 15 8 | -------------------------------------------------------------------------------- /source/basic/read-eval-print/src/invalid/syntax-error/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Example 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /source/basic/read-eval-print/src/empty/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | js-primer REPL 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /source/basic/read-eval-print/src/invalid/syntax-error-typo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Example 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /source/basic/string/src/invalid/regexp-literal.js: -------------------------------------------------------------------------------- 1 | // 正規表現リテラルはロード時にパターンが評価され、例外が発生する 2 | function main() { 3 | // `[`は対となる`]`を組み合わせる特殊文字であるため、単独で書けない 4 | const invalidPattern = /[/; 5 | } 6 | 7 | // `main`関数を呼び出さなくても例外が発生する -------------------------------------------------------------------------------- /source/use-case/nodecli/test/snapshots/md-to-html_src_main-1.js.txt: -------------------------------------------------------------------------------- 1 |

サンプルファイル

2 |

これはサンプルです。 3 | https://jsprimer.net/

4 | 8 | -------------------------------------------------------------------------------- /source/use-case/nodecli/test/snapshots/md-to-html_src_main.js--gfm.txt: -------------------------------------------------------------------------------- 1 |

サンプルファイル

2 |

これはサンプルです。 3 | https://jsprimer.net/

4 | 8 | -------------------------------------------------------------------------------- /resources/gitbook-plugin-include-codeblock.hbs: -------------------------------------------------------------------------------- 1 | {{#if title}} 2 |
3 |

{{title}}

4 |
5 | {{/if}} 6 | 7 | {{{backtick}}} {{lang}} 8 | {{{content}}} 9 | {{{backtick}}} -------------------------------------------------------------------------------- /source/use-case/nodecli/test/snapshots/md-to-html_src_main-3.js--gfm.txt: -------------------------------------------------------------------------------- 1 |

サンプルファイル

2 |

これはサンプルです。 3 | https://jsprimer.net/

4 | 8 | -------------------------------------------------------------------------------- /source/use-case/todoapp/screenshot-auto.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | declare currentDir=$(pwd) 4 | bash "${currentDir}/app-structure/todo-html/screenshot.sh" 5 | sleep 5 6 | bash "${currentDir}/entrypoint/first-entry/screenshot.sh" 7 | -------------------------------------------------------------------------------- /source/basic/loop/src/for-in/for-in-array-bug-example.js: -------------------------------------------------------------------------------- 1 | const numbers = [5, 10]; 2 | let total = 0; 3 | for (const num in numbers) { 4 | // 0 + "0" + "1" という文字列結合が行われる 5 | total += num; 6 | } 7 | console.log(total); // => "001" 8 | -------------------------------------------------------------------------------- /source/basic/module/src/default-import-with-named.js: -------------------------------------------------------------------------------- 1 | // myModuleとしてデフォルトインポートし、 2 | // fooを名前つきインポートする 3 | import myModule, { foo } from "./my-module.js"; 4 | console.log(foo); // => "foo" 5 | console.log(myModule); // => { baz: "baz" } 6 | -------------------------------------------------------------------------------- /source/basic/loop/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 無限ループ 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /source/use-case/nodecli/refactor-and-unittest/src/md2html.js: -------------------------------------------------------------------------------- 1 | import { marked } from "marked"; 2 | 3 | export function md2html(markdown, cliOptions) { 4 | return marked.parse(markdown, { 5 | gfm: cliOptions.gfm, 6 | }); 7 | }; 8 | -------------------------------------------------------------------------------- /source/use-case/nodecli/refactor-and-unittest/src/test/fixtures/expected-gfm.html: -------------------------------------------------------------------------------- 1 |

サンプルファイル

2 |

これはサンプルです。 3 | https://jsprimer.net/

4 | 8 | -------------------------------------------------------------------------------- /source/use-case/nodecli/test/snapshots/refactor-and-unittest_src_main.js--gfm.txt: -------------------------------------------------------------------------------- 1 |

サンプルファイル

2 |

これはサンプルです。 3 | https://jsprimer.net/

4 | 8 | -------------------------------------------------------------------------------- /source/basic/json/src/circular-reference.js: -------------------------------------------------------------------------------- 1 | const obj = { foo: "foo" }; 2 | obj.self = obj; 3 | try { 4 | JSON.stringify(obj); 5 | } catch (error) { 6 | console.error(error); // => "TypeError: Converting circular structure to JSON" 7 | } 8 | -------------------------------------------------------------------------------- /source/basic/loop/src/for/sum-for-each-example.js: -------------------------------------------------------------------------------- 1 | function sum(numbers) { 2 | let total = 0; 3 | numbers.forEach(num => { 4 | total += num; 5 | }); 6 | return total; 7 | } 8 | 9 | sum([1, 2, 3, 4, 5]); // => 15 10 | -------------------------------------------------------------------------------- /source/use-case/nodecli/test/snapshots/refactor-and-unittest_src_main-last.js--gfm.txt: -------------------------------------------------------------------------------- 1 |

サンプルファイル

2 |

これはサンプルです。 3 | https://jsprimer.net/

4 | 8 | -------------------------------------------------------------------------------- /source/use-case/todoapp/screenshot-manual.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | declare currentDir=$(pwd) 3 | # マニュアル操作が必要なもの 4 | bash "${currentDir}/form-event/prevent-event/screenshot.sh" 5 | sleep 5 6 | bash "${currentDir}/form-event/add-todo-item/screenshot.sh" 7 | -------------------------------------------------------------------------------- /source/basic/async/demo/try-catch-Function-callback.js: -------------------------------------------------------------------------------- 1 | try { 2 | // Functionはグロールスコープの直下で実行される 3 | new Function("throw new Error(\"message\");")(); 4 | } catch (error) { 5 | // catchできる 6 | console.log("Catch", error); // => Error: message 7 | } 8 | -------------------------------------------------------------------------------- /source/basic/string/src/invalid/regexp-constructor.js: -------------------------------------------------------------------------------- 1 | // `RegExp`コンストラクタは実行時にパターンが評価され、例外が発生する 2 | function main() { 3 | // `[`は対となる`]`を組み合わせる特殊文字であるため、単独で書けない 4 | const invalidPattern = new RegExp("["); 5 | } 6 | 7 | // `main`関数を呼び出すことで初めて例外が発生する 8 | main(); -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: 書籍のエラーや問題について報告する 4 | 5 | --- 6 | 7 | ## 該当ページ 8 | 9 | - ページのURLを書いてください 10 | 11 | ## 説明 12 | 13 | 17 | -------------------------------------------------------------------------------- /source/basic/async/demo/try-catch-sync-callback.js: -------------------------------------------------------------------------------- 1 | const ThrowFn = (callback) => { 2 | throw new Error("message"); 3 | callback(); 4 | }; 5 | try { 6 | ThrowFn(() => {}); 7 | } catch (error) { 8 | console.error(error); // => Error: message 9 | } 10 | -------------------------------------------------------------------------------- /source/basic/async/demo/try-catch-new-promise.js: -------------------------------------------------------------------------------- 1 | try { 2 | new Promise(() => { 3 | throw new Error("message"); 4 | }); 5 | } catch (error) { 6 | // UnhandledPromiseRejectionWarningとなりキャッチできない 7 | console.log("catch", error); // => Error: message 8 | } 9 | -------------------------------------------------------------------------------- /source/basic/loop/src/for/sum-for-example.js: -------------------------------------------------------------------------------- 1 | function sum(numbers) { 2 | let total = 0; 3 | for (let i = 0; i < numbers.length; i++) { 4 | total += numbers[i]; 5 | } 6 | return total; 7 | } 8 | 9 | console.log(sum([1, 2, 3, 4, 5])); // => 15 10 | -------------------------------------------------------------------------------- /source/basic/async/demo/try-catch-new-promise-re-throw.js: -------------------------------------------------------------------------------- 1 | try { 2 | new Promise(() => { 3 | throw new Error("message"); 4 | }); 5 | } catch (error) { 6 | // UnhandledPromiseRejectionWarningとなりキャッチできない 7 | console.log("catch", error); // => Error: message 8 | } 9 | -------------------------------------------------------------------------------- /.mcp.json: -------------------------------------------------------------------------------- 1 | { 2 | "mcpServers": { 3 | "textlint": { 4 | "type": "stdio", 5 | "command": "npx", 6 | "args": [ 7 | "textlint", 8 | "--mcp" 9 | ], 10 | "env": { 11 | "TEXTLINT_MCP": "true" 12 | } 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /source/basic/condition/src/if/else-if-example.js: -------------------------------------------------------------------------------- 1 | const version = "ES6"; 2 | if (version === "ES5") { 3 | console.log("ECMAScript 5"); 4 | } else if (version === "ES6") { 5 | console.log("ECMAScript 2015"); 6 | } else if (version === "ES7") { 7 | console.log("ECMAScript 2016"); 8 | } -------------------------------------------------------------------------------- /.vscode/mcp.json: -------------------------------------------------------------------------------- 1 | { 2 | "servers": { 3 | "textlint": { 4 | "type": "stdio", 5 | "command": "npx", 6 | "args": [ 7 | "textlint", 8 | "--mcp" 9 | ], 10 | "env": { 11 | "TEXTLINT_MCP": "true" 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /source/use-case/setup-local-env/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | index.html 6 | 7 | 8 |

ローカルサーバで配信中

9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /textlint/textlint-rule-inline-code-denylist/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "textlint-rule-inline-code-denylist", 3 | "version": "1.0.0", 4 | "description": "denylist for inline code", 5 | "main": "index.js", 6 | "keywords": [], 7 | "author": "azu", 8 | "license": "MIT" 9 | } 10 | -------------------------------------------------------------------------------- /source/basic/module/src/namespace-import.js: -------------------------------------------------------------------------------- 1 | // すべての名前つきエクスポートをmyModuleオブジェクトとしてまとめてインポートする 2 | import * as myModule from "./my-module.js"; 3 | // fooとして名前つきエクスポートされた値にアクセスする 4 | console.log(myModule.foo); // => "foo" 5 | // defaultとしてデフォルトエクスポートされた値にアクセスする 6 | console.log(myModule.default); // => { baz: "baz" } 7 | -------------------------------------------------------------------------------- /source/use-case/ajaxapp/entrypoint/example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Ajax Example 6 | 7 | 8 |

GitHub User Info

9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /source/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | author: azu 3 | fullTitle: "JavaScript Primer - 迷わないための入門書 #jsprimer" 4 | description: "JavaScriptの基本的な書き方からアプリケーションの作成などのユースケースを学ぶための入門書です" 5 | sponsors: [] 6 | --- 7 | 8 | 9 | {% include "./landing/index.html" %} 10 | 11 | -------------------------------------------------------------------------------- /source/use-case/nodecli/read-file/src/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodecli", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "nodecli", 9 | "version": "1.0.0", 10 | "license": "ISC" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /source/use-case/todoapp/entrypoint/first-entry/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Todo App 6 | 7 | 8 |

Todo App

9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /source/use-case/todoapp/entrypoint/module-entry/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Todo App 6 | 7 | 8 |

Todo App

9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /source/basic/error-try-catch/src/console/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | console.log 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /source/basic/error-try-catch/src/error-cause/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Error Cause 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /source/use-case/nodecli/argument-parse/src/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodecli", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "nodecli", 9 | "version": "1.0.0", 10 | "license": "ISC" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /source/use-case/nodecli/bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | declare nodecliDir=$(pwd) 3 | # npm install 4 | cd "${nodecliDir}/argument-parse/src" && npm install 5 | cd "${nodecliDir}/md-to-html/src" && npm install 6 | cd "${nodecliDir}/read-file/src" && npm install 7 | cd "${nodecliDir}/refactor-and-unittest/src" && npm install 8 | -------------------------------------------------------------------------------- /source/use-case/todoapp/form-event/add-todo-item/src/view/html-util-element-sample.js: -------------------------------------------------------------------------------- 1 | import { element } from "./html-util.js"; 2 | // HTML文字列からHTML要素を作成 3 | const newElement = element``; 6 | // 作成した要素を`document.body`の子要素として追加(appendChild)する 7 | document.body.appendChild(newElement); 8 | -------------------------------------------------------------------------------- /source/use-case/nodecli/helloworld/src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodecli", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "main.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC" 12 | } 13 | -------------------------------------------------------------------------------- /source/basic/prototype-object/OUTLINE.md: -------------------------------------------------------------------------------- 1 | # アウトライン 2 | 3 | - プロトタイプオブジェクト 4 | - 自動的に追加される 5 | - プロトタイプオブジェクトにはメソッドなどが実装されている 6 | - プロトタイプメソッド 7 | - インスタンスから呼び出しているのはプロトタイプメソッド 8 | - インスタンスに同じ名前のメソッドを実装した時 9 | - インスタンスのメソッドが優先される 10 | - インスタンスに同じ名前がない場合はプロトタイプメソッドが呼び出される 11 | - `in`演算子と`hasOwnProperty`メソッド 12 | -------------------------------------------------------------------------------- /source/basic/class/example/Counter.js: -------------------------------------------------------------------------------- 1 | class Counter { 2 | constructor() { 3 | this.count = 0; 4 | } 5 | 6 | increment() { 7 | this.count++; 8 | } 9 | } 10 | 11 | const counter = new Counter(); 12 | console.log(counter.count); // => 0 13 | counter.increment(); 14 | console.log(counter.count); // => 1 15 | -------------------------------------------------------------------------------- /source/basic/loop/src/for-in/object-keys-for-each-example.js: -------------------------------------------------------------------------------- 1 | const obj = { 2 | "a": 1, 3 | "b": 2, 4 | "c": 3 5 | }; 6 | Object.keys(obj).forEach(key => { 7 | const value = obj[key]; 8 | console.log(`key:${key}, value:${value}`); 9 | }); 10 | // "key:a, value:1" 11 | // "key:b, value:2" 12 | // "key:c, value:3" 13 | -------------------------------------------------------------------------------- /textlint/textlint-rule-no-use-column/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "textlint-rule-no-use-column", 3 | "version": "1.0.0", 4 | "description": "no use コラム", 5 | "main": "index.js", 6 | "keywords": [], 7 | "author": "azu", 8 | "license": "MIT", 9 | "dependencies": { 10 | "textlint-rule-helper": "^2.1.1" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /textlint/textlint-rule-static-method-syntax/README.md: -------------------------------------------------------------------------------- 1 | # 静的メソッドのtextlintルール 2 | 3 | ```markdown 4 | `Promise.resolve`メソッド 5 | ``` 6 | 7 | は 8 | 9 | ```markdown 10 | `Promise.resolve`静的メソッド 11 | ``` 12 | 13 | と記述するのをチェックするルールです。 14 | 15 | ## データの更新 16 | 17 | ```bash 18 | npm run update-data 19 | ``` 20 | 21 | で静的メソッドの一覧を更新できます 22 | -------------------------------------------------------------------------------- /textlint/textlint-rule-no-use-prototype-hash/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "textlint-rule-no-use-prototype-hash", 3 | "version": "1.0.0", 4 | "description": "no use # as prototype like Array#push", 5 | "main": "index.js", 6 | "keywords": [], 7 | "author": "azu", 8 | "license": "MIT", 9 | "dependencies": { 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /source/basic/iterator-generator/examples/basic/array-vs-iterator.example.js: -------------------------------------------------------------------------------- 1 | // 配列を使った場合:すべての数値を一度にメモリに作成 2 | const numbers = []; 3 | for (let i = 1; i <= 5000; i++) { 4 | numbers.push(i); 5 | } 6 | // 配列のサイズ 7 | console.log(numbers.length); // => 5000 8 | 9 | // すべてのデータがメモリに存在している 10 | console.log(numbers.slice(0, 5)); // => [1, 2, 3, 4, 5] 11 | -------------------------------------------------------------------------------- /source/basic/async/demo/try-catch-error-in-sync-callback.js: -------------------------------------------------------------------------------- 1 | /* 2 | コールバック内で起きたエラーだからcatchできなくなるわけではないという話 3 | */ 4 | const ThrowFn = (callback) => { 5 | callback(); 6 | }; 7 | 8 | try { 9 | ThrowFn(() => { 10 | throw new Error("message"); 11 | }); 12 | } catch (error) { 13 | console.error(error); // => Error: message 14 | } 15 | -------------------------------------------------------------------------------- /source/use-case/nodecli/argument-parse/src/main-2.js: -------------------------------------------------------------------------------- 1 | import * as util from "node:util"; 2 | 3 | // コマンドライン引数をparseArgs関数でパースする 4 | const { 5 | positionals 6 | } = util.parseArgs({ 7 | // オプションやフラグ以外の引数を渡すことを許可する 8 | allowPositionals: true 9 | }); 10 | // ファイルパスをpositionals配列から取り出す 11 | const filePath = positionals[0]; 12 | console.log(filePath); 13 | -------------------------------------------------------------------------------- /source/use-case/nodecli/read-file/src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodecli", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "main.js", 6 | "type": "module", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": {} 14 | } 15 | -------------------------------------------------------------------------------- /source/basic/async/demo/try-catch-setTimeout-callback.js: -------------------------------------------------------------------------------- 1 | try { 2 | // https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timer-initialisation-steps 3 | // setTimeoutはこのスコープではなくグローバルスコープで実行される 4 | setTimeout(() => { 5 | throw new Error("message"); 6 | }); 7 | } catch (error) { 8 | console.log(error); // => Error: message 9 | } 10 | -------------------------------------------------------------------------------- /source/basic/loop/src/for-in/for-in-object-example.js: -------------------------------------------------------------------------------- 1 | const obj = { 2 | "a": 1, 3 | "b": 2, 4 | "c": 3 5 | }; 6 | // 注記: ループのたびに毎回新しいブロックに変数keyが定義されるため、再定義エラーが発生しない 7 | for (const key in obj) { 8 | const value = obj[key]; 9 | console.log(`key:${key}, value:${value}`); 10 | } 11 | // "key:a, value:1" 12 | // "key:b, value:2" 13 | // "key:c, value:3" 14 | -------------------------------------------------------------------------------- /source/use-case/nodecli/argument-parse/src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodecli", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "main.js", 6 | "type": "module", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": {} 14 | } 15 | -------------------------------------------------------------------------------- /source/basic/iterator-generator/examples/basic/iterator.example.js: -------------------------------------------------------------------------------- 1 | // イテレータを使った場合:必要な時に値を生成 2 | function* numberGenerator() { 3 | for (let i = 1; i <= 5000; i++) { 4 | yield i; // 値を一つずつ生成 5 | } 6 | } 7 | 8 | const iterator = numberGenerator(); 9 | // 最初の値 10 | console.log(iterator.next().value); // => 1 11 | // 次の値 12 | console.log(iterator.next().value); // => 2 13 | -------------------------------------------------------------------------------- /source/use-case/nodecli/read-file/src/main-2.js: -------------------------------------------------------------------------------- 1 | import * as util from "node:util"; 2 | import * as fs from "node:fs/promises"; 3 | 4 | const { positionals } = util.parseArgs({ 5 | allowPositionals: true 6 | }); 7 | const filePath = positionals[0]; 8 | // ファイルをUTF-8として非同期で読み込む 9 | fs.readFile(filePath, { encoding: "utf8" }).then(file => { 10 | console.log(file); 11 | }); 12 | -------------------------------------------------------------------------------- /source/basic/loop/src/break/break-find-example.js: -------------------------------------------------------------------------------- 1 | const numbers = [1, 5, 10, 15, 20]; 2 | // 偶数があるかどうか 3 | let isEvenIncluded = false; 4 | for (let i = 0; i < numbers.length; i++) { 5 | const num = numbers[i]; 6 | // numが2で割り切れるなら偶数 7 | if (num % 2 === 0) { 8 | isEvenIncluded = true; 9 | break; 10 | } 11 | } 12 | console.log(isEvenIncluded); // => true 13 | -------------------------------------------------------------------------------- /source/use-case/ajaxapp/http/example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Ajax Example 6 | 7 | 8 |

GitHub User Info

9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /source/use-case/nodecli/refactor-and-unittest/src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodecli", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "main.js", 6 | "type": "module", 7 | "scripts": { 8 | "test": "node --test" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "marked": "^15.0.12" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: 書籍に対するリクエストや提案をする 4 | 5 | --- 6 | 7 | ## 該当ページ 8 | 9 | - [ ] 関係するページがあるならばURLを書いてください 10 | 11 | ## リクエスト/提案内容 12 | 13 | 17 | 18 | ## 期待する結果 19 | 20 | 21 | -------------------------------------------------------------------------------- /source/basic/class/example/Point.js: -------------------------------------------------------------------------------- 1 | class Point { 2 | constructor(x, y) { 3 | this.x = x; 4 | this.y = y; 5 | } 6 | 7 | toString() { 8 | return `x:${this.x}, y:${this.y}`; 9 | } 10 | } 11 | 12 | const point = new Point(3, 4); 13 | console.log(point.x); // => 3 14 | console.log(point.y); // => 4 15 | console.log(point.toString()); // => `x:3, y:4` 16 | -------------------------------------------------------------------------------- /source/use-case/todoapp/cypress/helper/todo-helper.js: -------------------------------------------------------------------------------- 1 | // MIT © 2018 azu 2 | "use strict"; 3 | /** 4 | * TODOアイテムを追加 5 | * @param {string} title 6 | * @returns {Cypress.Chainable>} 7 | */ 8 | const addNewTodo = (title) => { 9 | cy.get("#js-form-input").type(title); 10 | return cy.get("#js-form").submit(); 11 | }; 12 | 13 | module.exports.addNewTodo = addNewTodo; 14 | -------------------------------------------------------------------------------- /source/basic/function-this/this.js: -------------------------------------------------------------------------------- 1 | function getThis() { 2 | if (isArrowFunction()) { 3 | return "一つ外側の関数またはグローバルオブジェクト"; 4 | } else if (isGlobalFunction()) { 5 | if (isLexicalScope()) { 6 | return undefined; 7 | } else { 8 | return "global"; 9 | } 10 | } else { // function 11 | return "その関数が所属していたオブジェクト"; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /source/use-case/nodecli/md-to-html/src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodecli", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "main.js", 6 | "type": "module", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "marked": "^15.0.12" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /source/use-case/todoapp/final/final/src/model/TodoItemModel.example.js: -------------------------------------------------------------------------------- 1 | import { TodoItemModel } from "./TodoItemModel.js"; 2 | const item = new TodoItemModel({ 3 | title: "未完了のTodoアイテム", 4 | completed: false 5 | }); 6 | const completedItem = new TodoItemModel({ 7 | title: "完了済みのTodoアイテム", 8 | completed: true 9 | }); 10 | // それぞれの`id`は異なる 11 | console.log(item.id === completedItem.id); // => false 12 | -------------------------------------------------------------------------------- /source/use-case/todoapp/final/create-view/src/model/TodoItemModel.example.js: -------------------------------------------------------------------------------- 1 | import { TodoItemModel } from "./TodoItemModel.js"; 2 | const item = new TodoItemModel({ 3 | title: "未完了のTodoアイテム", 4 | completed: false 5 | }); 6 | const completedItem = new TodoItemModel({ 7 | title: "完了済みのTodoアイテム", 8 | completed: true 9 | }); 10 | // それぞれの`id`は異なる 11 | console.log(item.id === completedItem.id); // => false 12 | -------------------------------------------------------------------------------- /source/basic/condition/src/if/leap-year-flat-example.js: -------------------------------------------------------------------------------- 1 | const year = new Date().getFullYear(); 2 | if (year % 400 === 0) { // 400で割り切れる 3 | console.log(`${year}年はうるう年です`); 4 | } else if (year % 100 === 0) { // 100で割り切れる 5 | console.log(`${year}年はうるう年ではありません`); 6 | } else if (year % 4 === 0) { // 4で割り切れる 7 | console.log(`${year}年はうるう年です`); 8 | } else { // それ以外 9 | console.log(`${year}年はうるう年ではありません`); 10 | } 11 | -------------------------------------------------------------------------------- /source/use-case/todoapp/update-delete/add-checkbox/src/model/TodoItemModel.example.js: -------------------------------------------------------------------------------- 1 | import { TodoItemModel } from "./TodoItemModel.js"; 2 | const item = new TodoItemModel({ 3 | title: "未完了のTodoアイテム", 4 | completed: false 5 | }); 6 | const completedItem = new TodoItemModel({ 7 | title: "完了済みのTodoアイテム", 8 | completed: true 9 | }); 10 | // それぞれの`id`は異なる 11 | console.log(item.id === completedItem.id); // => false 12 | -------------------------------------------------------------------------------- /source/use-case/todoapp/update-delete/delete-feature/src/model/TodoItemModel.example.js: -------------------------------------------------------------------------------- 1 | import { TodoItemModel } from "./TodoItemModel.js"; 2 | const item = new TodoItemModel({ 3 | title: "未完了のTodoアイテム", 4 | completed: false 5 | }); 6 | const completedItem = new TodoItemModel({ 7 | title: "完了済みのTodoアイテム", 8 | completed: true 9 | }); 10 | // それぞれの`id`は異なる 11 | console.log(item.id === completedItem.id); // => false 12 | -------------------------------------------------------------------------------- /source/use-case/todoapp/update-delete/update-feature/src/model/TodoItemModel.example.js: -------------------------------------------------------------------------------- 1 | import { TodoItemModel } from "./TodoItemModel.js"; 2 | const item = new TodoItemModel({ 3 | title: "未完了のTodoアイテム", 4 | completed: false 5 | }); 6 | const completedItem = new TodoItemModel({ 7 | title: "完了済みのTodoアイテム", 8 | completed: true 9 | }); 10 | // それぞれの`id`は異なる 11 | console.log(item.id === completedItem.id); // => false 12 | -------------------------------------------------------------------------------- /source/basic/async/demo/try-catch-error-generator.js: -------------------------------------------------------------------------------- 1 | function* gen() { 2 | while (true) { 3 | yield 42; 4 | } 5 | } 6 | 7 | try { 8 | const g = gen(); 9 | setTimeout(() => { 10 | g.next(); // { value: 42, done: false } 11 | g.throw(new Error("message")); 12 | }); 13 | } catch (error) { 14 | // キャッチできる 15 | // g.throwによってCompletionが伝播する 16 | console.log("catch", error); 17 | } 18 | -------------------------------------------------------------------------------- /source/basic/loop/src/break/find-even-return-example.js: -------------------------------------------------------------------------------- 1 | function isEven(num) { 2 | return num % 2 === 0; 3 | } 4 | function isEvenIncluded(numbers) { 5 | for (let i = 0; i < numbers.length; i++) { 6 | const num = numbers[i]; 7 | if (isEven(num)) { 8 | return true; 9 | } 10 | } 11 | return false; 12 | } 13 | const numbers = [1, 5, 10, 15, 20]; 14 | console.log(isEvenIncluded(numbers)); // => true 15 | -------------------------------------------------------------------------------- /source/use-case/todoapp/cypress/integration/entrypoint/first-entry/first-entry-spec.js: -------------------------------------------------------------------------------- 1 | const URL = "/entrypoint/first-entry"; 2 | const visitWithConsole = require("../../../helper/visit-with-console").visitWithConsole; 3 | describe(URL, function() { 4 | it("ロードするとログが表示される", function() { 5 | visitWithConsole(URL).then(({ logSpy }) => { 6 | expect(logSpy).to.be.calledWith("index.js: loaded"); 7 | }); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /source/use-case/todoapp/event-model/event-emitter/src/EventEmitter.example.js: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from "./EventEmitter.js"; 2 | const event = new EventEmitter(); 3 | // イベントリスナー(コールバック関数)を登録 4 | event.addEventListener("test-event", () => console.log("One!")); 5 | event.addEventListener("test-event", () => console.log("Two!")); 6 | // イベントをディスパッチする 7 | event.emit("test-event"); 8 | // コールバック関数がそれぞれ呼びだされ、コンソールには次のように出力される 9 | // "One!" 10 | // "Two!" -------------------------------------------------------------------------------- /source/basic/condition/src/switch/switch-example.js: -------------------------------------------------------------------------------- 1 | const version = "ES6"; 2 | switch (version) { 3 | case "ES5": 4 | console.log("ECMAScript 5"); 5 | break; 6 | case "ES6": 7 | console.log("ECMAScript 2015"); 8 | break; 9 | case "ES7": 10 | console.log("ECMAScript 2016"); 11 | break; 12 | default: 13 | console.log("しらないバージョンです"); 14 | break; 15 | } 16 | // "ECMAScript 2015" と出力される 17 | -------------------------------------------------------------------------------- /source/use-case/ajaxapp/display/example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Ajax Example 6 | 7 | 8 |

GitHub User Info

9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /source/use-case/ajaxapp/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Ajax Example 6 | 7 | 8 |

GitHub User Info

9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /source/use-case/nodecli/read-file/src/main-1.js: -------------------------------------------------------------------------------- 1 | // utilモジュールをutilオブジェクトとしてインポートする 2 | import * as util from "node:util"; 3 | // fs/promisesモジュールをfsオブジェクトとしてインポートする 4 | import * as fs from "node:fs/promises"; 5 | 6 | // コマンドライン引数からファイルパスを取得する 7 | const { positionals } = util.parseArgs({ 8 | allowPositionals: true 9 | }); 10 | const filePath = positionals[0]; 11 | // ファイルを非同期で読み込む 12 | fs.readFile(filePath).then(file => { 13 | console.log(file); 14 | }); 15 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | # Unix-style newlines with a newline ending every file 4 | [*] 5 | end_of_line = lf 6 | insert_final_newline = true 7 | 8 | [*.md] 9 | charset = utf-8 10 | indent_style = space 11 | indent_size = 4 12 | 13 | [*.js] 14 | charset = utf-8 15 | indent_style = space 16 | indent_size = 4 17 | 18 | # Matches the exact files either package.json or .travis.yml 19 | [{package.json,.travis.yml}] 20 | indent_style = space 21 | indent_size = 2 22 | -------------------------------------------------------------------------------- /source/basic/condition/src/switch/switch-return-example.js: -------------------------------------------------------------------------------- 1 | function getECMAScriptName(version) { 2 | switch (version) { 3 | case "ES5": 4 | return "ECMAScript 5"; 5 | case "ES6": 6 | return "ECMAScript 2015"; 7 | case "ES7": 8 | return "ECMAScript 2016"; 9 | default: 10 | return "しらないバージョンです"; 11 | } 12 | } 13 | // 関数を実行して`return`された値を得る 14 | getECMAScriptName("ES6"); // => "ECMAScript 2015" -------------------------------------------------------------------------------- /source/use-case/ajaxapp/promise/example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Ajax Example 6 | 7 | 8 |

GitHub User Info

9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /source/use-case/todoapp/form-event/prevent-event/src/App.js: -------------------------------------------------------------------------------- 1 | export class App { 2 | mount() { 3 | const formElement = document.querySelector("#js-form"); 4 | const inputElement = document.querySelector("#js-form-input"); 5 | formElement.addEventListener("submit", (event) => { 6 | // submitイベントの本来の動作を止める 7 | event.preventDefault(); 8 | console.log(`入力欄の値: ${inputElement.value}`); 9 | }); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /textlint/textlint-rule-static-method-syntax/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "textlint-rule-static-method-syntax", 3 | "version": "1.0.0", 4 | "description": "静的メソッドの書き方", 5 | "type": "module", 6 | "main": "./index.js", 7 | "exports": "./index.js", 8 | "keywords": [], 9 | "author": "azu", 10 | "license": "MIT", 11 | "scripts": { 12 | "update-data": "node ./update-static-method.js" 13 | }, 14 | "dependencies": { 15 | "@textlint/types": "^14.6.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /source/basic/class/example/ConflictClass.js: -------------------------------------------------------------------------------- 1 | class ConflictClass { 2 | constructor() { 3 | this.method = () => { 4 | console.log("インスタンスのメソッド"); 5 | }; 6 | } 7 | 8 | method() { 9 | console.log("プロトタイプメソッド"); 10 | } 11 | } 12 | 13 | const conflict = new ConflictClass(); 14 | conflict.method(); // "インスタンスのメソッド" 15 | // インスタンスの`method`プロパティを削除 16 | delete conflict.method; 17 | // プロトタイプメソッドの`method`が呼ばれるようになる 18 | conflict.method(); // "プロトタイプメソッド" 19 | -------------------------------------------------------------------------------- /source/basic/condition/src/if/leap-year-nest-example.js: -------------------------------------------------------------------------------- 1 | const year = new Date().getFullYear(); 2 | if (year % 4 === 0) { // 4で割り切れる 3 | if (year % 100 === 0) { // 100で割り切れる 4 | if (year % 400 === 0) { // 400で割り切れる 5 | console.log(`${year}年はうるう年です`); 6 | } else { 7 | console.log(`${year}年はうるう年ではありません`); 8 | } 9 | } else { 10 | console.log(`${year}年はうるう年です`); 11 | } 12 | } else { 13 | console.log(`${year}年はうるう年ではありません`); 14 | } 15 | -------------------------------------------------------------------------------- /source/basic/string-unicode/img/codeunit-codepoint-table.txt: -------------------------------------------------------------------------------- 1 |
インデックス012
文字列
UnicodeのCode Point(16進数)0x30A20x30AA0x30A4
UTF-16のCode Unit(16進数)0x30A20x30AA0x30A4
-------------------------------------------------------------------------------- /source/use-case/nodecli/read-file/src/main-3.js: -------------------------------------------------------------------------------- 1 | import * as util from "node:util"; 2 | import * as fs from "node:fs/promises"; 3 | 4 | const { positionals } = util.parseArgs({ 5 | allowPositionals: true 6 | }); 7 | const filePath = positionals[0]; 8 | // ファイルを非同期で読み込む 9 | fs.readFile(filePath, { encoding: "utf8" }).then(file => { 10 | console.log(file); 11 | }).catch(err => { 12 | console.error(err.message); 13 | // 終了ステータス 1(一般的なエラー)としてプロセスを終了する 14 | process.exit(1); 15 | }); 16 | -------------------------------------------------------------------------------- /source/basic/condition/src/switch/miss-case-example.js: -------------------------------------------------------------------------------- 1 | const version = "ES6"; 2 | switch (version) { 3 | case "ES5": 4 | console.log("ECMAScript 5"); 5 | case "ES6": // 一致するケース 6 | console.log("ECMAScript 2015"); 7 | case "ES7": // breakされないため条件無視して実行 8 | console.log("ECMAScript 2016"); 9 | default: // breakされないため条件無視して実行 10 | console.log("しらないバージョンです"); 11 | } 12 | /* 13 | "ECMAScript 2015" 14 | "ECMAScript 2016" 15 | "しらないバージョンです" 16 | と出力される 17 | */ -------------------------------------------------------------------------------- /source/use-case/todoapp/form-event/add-todo-item/src/view/html-util-render-sample.js: -------------------------------------------------------------------------------- 1 | import { element, render } from "./html-util.js"; 2 | // renderの前に、要素をdocument.bodyへ追加する 3 | const oldElement = element``; 6 | document.body.appendChild(oldElement); 7 | // 新しい要素を作成する 8 | const newElement = element``; 11 | // `newElement`を`document.body`の子要素として追加する 12 | // すでに`document.body`以下にある要素は上書きされる 13 | render(newElement, document.body); 14 | -------------------------------------------------------------------------------- /source/use-case/nodecli/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "nodecli", 4 | "version": "1.0.0", 5 | "description": "nodecli usecase", 6 | "scripts": { 7 | "pretest": "bash bootstrap.sh", 8 | "updateSnapshot": "UPDATE_SNAPSHOT=1 npm run unitest", 9 | "test": "npm run unitest", 10 | "unitest": "node --test test/test.mjs" 11 | }, 12 | "keywords": [], 13 | "author": "azu", 14 | "license": "MIT", 15 | "devDependencies": { 16 | "execa": "^9.6.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /source/use-case/todoapp/entrypoint/module-scope/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Module Scope 6 | 7 | 8 | 11 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /source/basic/async/demo/Task-error.js: -------------------------------------------------------------------------------- 1 | class Task { 2 | add(callback) { 3 | this.callback = callback; 4 | } 5 | 6 | invoke() { 7 | this.callback(); 8 | } 9 | } 10 | 11 | const task = new Task(); 12 | try { 13 | task.add(() => { 14 | throw new Error("message"); 15 | }); 16 | } catch (error) { 17 | console.log("Can not catch", error); 18 | } 19 | // ng 20 | // task.invoke(); 21 | // ok 22 | try { 23 | task.invoke(); 24 | } catch (error) { 25 | console.log("catch", error); 26 | } 27 | -------------------------------------------------------------------------------- /source/use-case/todoapp/cypress/helper/visit-with-console.js: -------------------------------------------------------------------------------- 1 | // MIT © 2018 azu 2 | "use strict"; 3 | /** 4 | * @param {string} URL 5 | * @returns {Promise<{ window: Window, logSpy: *}>} 6 | */ 7 | exports.visitWithConsole = (URL) => { 8 | let logSpy; 9 | return cy.visit(URL, { 10 | onBeforeLoad: (win) => { 11 | logSpy = cy.spy(win.console, "log").as("log"); 12 | } 13 | }).then(win => { 14 | return { 15 | window: win, 16 | logSpy: logSpy 17 | }; 18 | }); 19 | }; 20 | -------------------------------------------------------------------------------- /source/basic/class/example/Car.js: -------------------------------------------------------------------------------- 1 | class Car { 2 | constructor(maker, serialNumber) { 3 | this.maker = maker; 4 | this.serialNumber = serialNumber; 5 | } 6 | 7 | isEquals(car) { 8 | return car.maker === this.maker && car.serialNumber === this.serialNumber; 9 | } 10 | } 11 | 12 | // main 13 | const car1 = new Car("Tesla", "Model S"); 14 | const car2 = new Car("Mazda", "3i"); 15 | const car3 = new Car("Mazda", "3i"); 16 | console.log(car1.isEquals(car2)); // => false 17 | console.log(car2.isEquals(car3)); // => true 18 | -------------------------------------------------------------------------------- /source/basic/class/example/Prototype.js: -------------------------------------------------------------------------------- 1 | /** 2 | * - インスタンス: クラスから生成される 3 | * - クラス(コンストラクタ): クラスを定義 4 | * - プロトタイプ: クラスの定義時に自動的に作成される 5 | */ 6 | class MyClass { 7 | method() { 8 | // メソッドの処理 9 | } 10 | } 11 | 12 | // インスタンス を クラス から作る 13 | const myInstance = new MyClass(); 14 | // インスタンス から クラス を取得する 15 | console.log(myInstance.constructor === MyClass); 16 | // インスタンス から クラスのプロトタイプ を取得する 17 | console.log(Object.getPrototypeOf(myInstance) === MyClass.prototype); 18 | // インスタンスのプロトタイプ 19 | console.log(myInstance.prototype); // => undefined 20 | -------------------------------------------------------------------------------- /source/use-case/todoapp/event-model/event-emitter/src/model/TodoItemModel.example.js: -------------------------------------------------------------------------------- 1 | import assert from "node:assert"; 2 | //! [main] 3 | import { TodoItemModel } from "./TodoItemModel.js"; 4 | const item = new TodoItemModel({ 5 | title: "未完了のTodoアイテム", 6 | completed: false 7 | }); 8 | const completedItem = new TodoItemModel({ 9 | title: "完了済みのTodoアイテム", 10 | completed: true 11 | }); 12 | // それぞれの`id`は異なる 13 | console.log(item.id !== completedItem.id); // => true 14 | //! [main] 15 | // test 16 | assert.notStrictEqual(item.id, completedItem.id); 17 | -------------------------------------------------------------------------------- /source/basic/class/example/prototype-call.js: -------------------------------------------------------------------------------- 1 | class MyClass { 2 | method() { 3 | console.log("プロトタイプオブジェクト"); 4 | } 5 | } 6 | 7 | // インスタンス を クラス から作る 8 | const myInstance = new MyClass(); 9 | 10 | // 再帰表現 11 | function call(obj, methodName) { 12 | if (obj.hasOwnProperty(methodName)) { 13 | return obj[methodName](); 14 | } 15 | const prototypeObject = Object.getPrototypeOf(obj); 16 | if (!prototypeObject) { 17 | return; 18 | } 19 | return call(prototypeObject, methodName); 20 | } 21 | 22 | call(myInstance, "method"); 23 | -------------------------------------------------------------------------------- /source/basic/loop/src/break/find-even-break-example.js: -------------------------------------------------------------------------------- 1 | // 引数の`num`が偶数ならtrueを返す 2 | function isEven(num) { 3 | return num % 2 === 0; 4 | } 5 | // 引数の`numbers`に偶数が含まれているならtrueを返す 6 | function isEvenIncluded(numbers) { 7 | let isEvenIncluded = false; 8 | for (let i = 0; i < numbers.length; i++) { 9 | const num = numbers[i]; 10 | if (isEven(num)) { 11 | isEvenIncluded = true; 12 | break; 13 | } 14 | } 15 | return isEvenIncluded; 16 | } 17 | const array = [1, 5, 10, 15, 20]; 18 | console.log(isEvenIncluded(array)); // => true 19 | -------------------------------------------------------------------------------- /source/use-case/nodecli/md-to-html/src/main-2.js: -------------------------------------------------------------------------------- 1 | import * as util from "node:util"; 2 | import * as fs from "node:fs/promises"; 3 | import { marked } from "marked"; 4 | 5 | const { 6 | positionals 7 | } = util.parseArgs({ 8 | allowPositionals: true, 9 | }); 10 | const filePath = positionals[0]; 11 | fs.readFile(filePath, { encoding: "utf8" }).then(file => { 12 | // gfmオプションを無効にする 13 | const html = marked.parse(file, { 14 | gfm: false 15 | }); 16 | console.log(html); 17 | }).catch(err => { 18 | console.error(err.message); 19 | process.exit(1); 20 | }); 21 | -------------------------------------------------------------------------------- /source/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 404 Not Found · JavaScript Primer #jsprimer 7 | 8 | 9 | 10 | 11 |
12 |

404 Not Found

13 |

このページは存在しません

14 |

JavaScript Primerへ戻る

15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /source/use-case/nodecli/md-to-html/src/main-1.js: -------------------------------------------------------------------------------- 1 | import * as util from "node:util"; 2 | import * as fs from "node:fs/promises"; 3 | // markedモジュールからmarkedオブジェクトをインポートする 4 | import { marked } from "marked"; 5 | 6 | const { 7 | positionals 8 | } = util.parseArgs({ 9 | allowPositionals: true, 10 | }); 11 | const filePath = positionals[0]; 12 | fs.readFile(filePath, { encoding: "utf8" }).then(file => { 13 | // MarkdownファイルをHTML文字列に変換する 14 | const html = marked.parse(file); 15 | console.log(html); 16 | }).catch(err => { 17 | console.error(err.message); 18 | process.exit(1); 19 | }); 20 | -------------------------------------------------------------------------------- /source/basic/class/example/EventEmitter.js: -------------------------------------------------------------------------------- 1 | class EventEmitter { 2 | constructor() { 3 | this.eventHandlers = []; 4 | } 5 | 6 | addEventListener(eventHandler) { 7 | this.eventHandlers.push(eventHandler); 8 | } 9 | 10 | emit(...args) { 11 | this.eventHandlers.forEach(handler => { 12 | handler(...args); 13 | }); 14 | } 15 | } 16 | 17 | const event = new EventEmitter(); 18 | // listen 19 | event.addEventListener(() => console.log("Hi")); 20 | event.addEventListener((...args) => console.log("Hello", ...args)); 21 | // emit 22 | event.emit("John"); 23 | -------------------------------------------------------------------------------- /source/basic/string-unicode/img/emoji-codeunit-codepoint.txt: -------------------------------------------------------------------------------- 1 |
インデックス01234
文字列


🍎
UnicodeのCode Point(16進数)0x30ea0x30f30x30b40x1f34e
UTF-16のCode Unit(16進数)0x30ea0x30f30x30b40xd83c0xdf4e
-------------------------------------------------------------------------------- /source/use-case/todoapp/cypress/integration/entrypoint/module-entry/module-entry-spec.js: -------------------------------------------------------------------------------- 1 | const URL = "/entrypoint/module-entry"; 2 | const visitWithConsole = require("../../../helper/visit-with-console").visitWithConsole; 3 | describe(URL, function() { 4 | it("ロードするとApp.jsのログが表示される", function() { 5 | visitWithConsole(URL).then(({ logSpy }) => { 6 | const log0 = logSpy.getCall(0).args[0]; 7 | const log1 = logSpy.getCall(1).args[0]; 8 | expect(log0).to.equal("App.js: loaded"); 9 | expect(log1).to.equal("App initialized"); 10 | }); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /source/cheetsheet/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Redirecting... Page moved 6 | 7 | 8 | 9 | 10 |

Redirecting... Page moved...

11 |

Click here if you are not redirected

12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /source/basic/loop/src/continue/continue-filter-even-example.js: -------------------------------------------------------------------------------- 1 | // `number`が偶数ならtrueを返す 2 | function isEven(num) { 3 | return num % 2 === 0; 4 | } 5 | // `numbers`に含まれている偶数だけを取り出す 6 | function filterEven(numbers) { 7 | const results = []; 8 | for (let i = 0; i < numbers.length; i++) { 9 | const num = numbers[i]; 10 | // 偶数ではないなら、次のループへ 11 | if (!isEven(num)) { 12 | continue; 13 | } 14 | // 偶数を`results`に追加 15 | results.push(num); 16 | } 17 | return results; 18 | } 19 | const array = [1, 5, 10, 15, 20]; 20 | console.log(filterEven(array)); // => [10, 20] 21 | -------------------------------------------------------------------------------- /source/basic/class/example/MyClass.js: -------------------------------------------------------------------------------- 1 | // クラス宣言文 2 | class MyClass1 { 3 | constructor() { 4 | 5 | } 6 | } 7 | 8 | const myInstance1 = new MyClass1(); 9 | 10 | // クラス宣言式 11 | // `class クラス名` クラス名は省略できる 12 | const MyClass2 = class { 13 | constructor() { 14 | 15 | } 16 | }; 17 | const myInstance2 = new MyClass2(); 18 | 19 | // # constructorの省略 20 | // `constructor`は何もしないなら省略できる 21 | class MyClassShort1 {} 22 | 23 | const myInstance3 = new MyClassShort1(); 24 | 25 | // # クラスは関数として呼べない 26 | // 一方クラスは関数として呼び出すことはできない 27 | MyClassShort1();// => TypeError: Class constructor MyClassShort1 cannot be invoked without 'new' 28 | -------------------------------------------------------------------------------- /source/use-case/ajaxapp/http/example/index.js: -------------------------------------------------------------------------------- 1 | function fetchUserInfo(userId) { 2 | fetch(`https://api.github.com/users/${encodeURIComponent(userId)}`) 3 | .then(response => { 4 | console.log(response.status); 5 | // エラーレスポンスが返されたことを検知する 6 | if (!response.ok) { 7 | console.error("エラーレスポンス", response); 8 | } else { 9 | return response.json().then(userInfo => { 10 | console.log(userInfo); 11 | }); 12 | } 13 | }).catch(error => { 14 | console.error(error); 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /source/use-case/ajaxapp/xhr/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Redirecting... Page moved 6 | 7 | 8 | 9 | 10 | 11 |

Redirecting... Page moved...

12 |

Click here if you are not redirected

13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /source/use-case/todoapp/final/create-view/src/model/TodoItemModel.js: -------------------------------------------------------------------------------- 1 | // ユニークなIDを管理する変数 2 | let todoIdx = 0; 3 | 4 | export class TodoItemModel { 5 | /** @type {number} TodoアイテムのID */ 6 | id; 7 | /** @type {string} Todoアイテムのタイトル */ 8 | title; 9 | /** @type {boolean} Todoアイテムが完了済みならばtrue、そうでない場合はfalse */ 10 | completed; 11 | /** 12 | * @param {{ title: string, completed: boolean }} 13 | */ 14 | constructor({ title, completed }) { 15 | // idは連番となり、それぞれのインスタンス毎に異なるものとする 16 | this.id = todoIdx++; 17 | this.title = title; 18 | this.completed = completed; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /source/use-case/todoapp/final/final/src/model/TodoItemModel.js: -------------------------------------------------------------------------------- 1 | // ユニークなIDを管理する変数 2 | let todoIdx = 0; 3 | 4 | export class TodoItemModel { 5 | /** @type {number} TodoアイテムのID */ 6 | id; 7 | /** @type {string} Todoアイテムのタイトル */ 8 | title; 9 | /** @type {boolean} Todoアイテムが完了済みならばtrue、そうでない場合はfalse */ 10 | completed; 11 | 12 | /** 13 | * @param {{ title: string, completed: boolean }} 14 | */ 15 | constructor({ title, completed }) { 16 | // idは連番となり、それぞれのインスタンス毎に異なるものとする 17 | this.id = todoIdx++; 18 | this.title = title; 19 | this.completed = completed; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /source/use-case/todoapp/update-delete/input-checkbox/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | input checkbox 6 | 15 | 16 | 17 | 18 |
19 | 20 | 21 |
22 | 23 | 24 | -------------------------------------------------------------------------------- /source/use-case/todoapp/event-model/event-emitter/src/model/TodoItemModel.js: -------------------------------------------------------------------------------- 1 | // ユニークなIDを管理する変数 2 | let todoIdx = 0; 3 | 4 | export class TodoItemModel { 5 | /** @type {number} TodoアイテムのID */ 6 | id; 7 | /** @type {string} Todoアイテムのタイトル */ 8 | title; 9 | /** @type {boolean} Todoアイテムが完了済みならばtrue、そうでない場合はfalse */ 10 | completed; 11 | 12 | /** 13 | * @param {{ title: string, completed: boolean }} 14 | */ 15 | constructor({ title, completed }) { 16 | // idは連番となり、それぞれのインスタンス毎に異なるものとする 17 | this.id = todoIdx++; 18 | this.title = title; 19 | this.completed = completed; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /source/use-case/todoapp/update-delete/add-checkbox/src/model/TodoItemModel.js: -------------------------------------------------------------------------------- 1 | // ユニークなIDを管理する変数 2 | let todoIdx = 0; 3 | 4 | export class TodoItemModel { 5 | /** @type {number} TodoアイテムのID */ 6 | id; 7 | /** @type {string} Todoアイテムのタイトル */ 8 | title; 9 | /** @type {boolean} Todoアイテムが完了済みならばtrue、そうでない場合はfalse */ 10 | completed; 11 | 12 | /** 13 | * @param {{ title: string, completed: boolean }} 14 | */ 15 | constructor({ title, completed }) { 16 | // idは連番となり、それぞれのインスタンス毎に異なるものとする 17 | this.id = todoIdx++; 18 | this.title = title; 19 | this.completed = completed; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /source/use-case/todoapp/update-delete/delete-feature/src/model/TodoItemModel.js: -------------------------------------------------------------------------------- 1 | // ユニークなIDを管理する変数 2 | let todoIdx = 0; 3 | 4 | export class TodoItemModel { 5 | /** @type {number} TodoアイテムのID */ 6 | id; 7 | /** @type {string} Todoアイテムのタイトル */ 8 | title; 9 | /** @type {boolean} Todoアイテムが完了済みならばtrue、そうでない場合はfalse */ 10 | completed; 11 | 12 | /** 13 | * @param {{ title: string, completed: boolean }} 14 | */ 15 | constructor({ title, completed }) { 16 | // idは連番となり、それぞれのインスタンス毎に異なるものとする 17 | this.id = todoIdx++; 18 | this.title = title; 19 | this.completed = completed; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /source/use-case/todoapp/update-delete/update-feature/src/model/TodoItemModel.js: -------------------------------------------------------------------------------- 1 | // ユニークなIDを管理する変数 2 | let todoIdx = 0; 3 | 4 | export class TodoItemModel { 5 | /** @type {number} TodoアイテムのID */ 6 | id; 7 | /** @type {string} Todoアイテムのタイトル */ 8 | title; 9 | /** @type {boolean} Todoアイテムが完了済みならばtrue、そうでない場合はfalse */ 10 | completed; 11 | 12 | /** 13 | * @param {{ title: string, completed: boolean }} 14 | */ 15 | constructor({ title, completed }) { 16 | // idは連番となり、それぞれのインスタンス毎に異なるものとする 17 | this.id = todoIdx++; 18 | this.title = title; 19 | this.completed = completed; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /source/use-case/todoapp/final/more/index.js: -------------------------------------------------------------------------------- 1 | import { App } from "./src/App.js"; 2 | 3 | const formElement = document.querySelector("#js-form"); 4 | const formInputElement = document.querySelector("#js-form-input"); 5 | const todoCountElement = document.querySelector("#js-todo-count"); 6 | const todoListContainerElement = document.querySelector("#js-todo-list"); 7 | 8 | const app = new App({ 9 | formElement, 10 | formInputElement, 11 | todoCountElement, 12 | todoListContainerElement 13 | }); 14 | window.addEventListener("load", () => { 15 | app.mount(); 16 | }); 17 | window.addEventListener("unload", () => { 18 | app.unmount(); 19 | }); 20 | -------------------------------------------------------------------------------- /tools/textstat.cjs: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const cli = require("textstat").cli; 3 | const getFilePathListAsync = require("gitbook-summary-to-path").getFilePathListAsync; 4 | const sourceDir = path.join(__dirname, "../source"); 5 | const OUTLINE = path.join(sourceDir, "README.md"); 6 | getFilePathListAsync(OUTLINE).then(fileList => { 7 | return cli.run({ 8 | globPatterns: fileList.filter(filePath => filePath !== OUTLINE), 9 | format: "json", 10 | locale: "en" 11 | }).then(output => { 12 | console.log(output); 13 | }).catch(error => { 14 | console.error(error); 15 | process.exit(1); 16 | }); 17 | }); -------------------------------------------------------------------------------- /source/basic/iterator-generator/examples/protocol/manual-iteration.example.js: -------------------------------------------------------------------------------- 1 | function createRange(start, end) { 2 | let current = start; 3 | return { 4 | next() { 5 | if (current <= end) { 6 | return { value: current++, done: false }; 7 | } else { 8 | return { value: undefined, done: true }; 9 | } 10 | }, 11 | [Symbol.iterator]() { 12 | return this; 13 | } 14 | }; 15 | } 16 | 17 | const range = createRange(1, 3); 18 | 19 | // Iteratorが{ done: true }を返すまで、`next`メソッドし、その`value`を取得する 20 | for (const num of range) { 21 | console.log(num); // 1, 2, 3 22 | } -------------------------------------------------------------------------------- /source/basic/read-eval-print/screenshot.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | declare projectDir=$(git rev-parse --show-toplevel); 4 | declare currentDir=$(pwd) 5 | declare screenshotDevTools="${projectDir}/tools/applescript/lib/src/screenshot-dev-tools.js"; 6 | declare screenshot="${projectDir}/tools/applescript/lib/src/screenshot.js"; 7 | declare launchFirefox="${projectDir}/tools/applescript/lib/src/launch-firefox.js"; 8 | declare screenshotOnly="${projectDir}/tools/applescript/lib/src/screenshot-only.js"; 9 | 10 | # entry-pointのスクショ 11 | mkdir -p "${currentDir}/img/" 12 | node "${screenshotDevTools}" --url "https://jsprimer.net/basic/read-eval-print/src/empty/" --output "${currentDir}/img/web-console.png" 13 | -------------------------------------------------------------------------------- /source/use-case/ajaxapp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "plain-ajax", 3 | "version": "1.0.0", 4 | "description": "Ajax Example", 5 | "main": "index.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "start": "static src", 11 | "start:entrypoint": "static entrypoint/src", 12 | "start:xhr": "static xhr/src", 13 | "start:display": "static display/src", 14 | "start:promise": "static promise/src", 15 | "firefox": "osascript firefox-launch.applescript", 16 | "ss": "osascript screenshot.applescript" 17 | }, 18 | "keywords": [], 19 | "author": "laco", 20 | "license": "MIT", 21 | "devDependencies": { 22 | "node-static": "^0.7.7" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /source/outro/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | author: azu 3 | description: "書籍を読み終わった感想やフィードバックの送り先、リンクの貼り方についての紹介です。" 4 | sponsors: [] 5 | --- 6 | 7 | # おわりに {#outro} 8 | 9 | この書籍はJavaScriptを一から学べることを目的に書かれています。 10 | この書籍がJavaScriptを学ぶ上で助けになれば幸いです。 11 | 12 | この書籍のウェブサイトはすべてのページにURLがあり、セクションのタイトルにもパーマネントリンクがあります。 13 | このウェブサイトへのリンクは自由に貼れるため、必要に応じてご活用ください。 14 | 15 | もし書籍中に間違いや疑問に思った箇所を見つけた場合は、「[文章の間違いに気づいたら][]」を参考にお知らせください。 16 | 17 | また書籍を読んだ感想をブログやXなどに書いてくれるとうれしいです。 18 | 19 | - Xのハッシュタグ: [#jsprimer](https://x.com/search?f=realtime&q=%23jsprimer) 20 | 21 | 読んだ感想やフィードバックを直接送りたい場合は、次のフィードバックフォームをご利用ください。 22 | 23 | - [jsprimer.netの感想/フィードバック](https://forms.gle/YAqr1oPBs1KShFSPA) 24 | 25 | [文章の間違いに気づいたら]: ../intro/feedback/README.md 26 | -------------------------------------------------------------------------------- /source/use-case/todoapp/update-delete/add-checkbox/src/model/TodoListModel.example.js: -------------------------------------------------------------------------------- 1 | import { TodoItemModel } from "./TodoItemModel.js"; 2 | import { TodoListModel } from "./TodoListModel.js"; 3 | // 新しいTodoリストを作成する 4 | const todoListModel = new TodoListModel(); 5 | // 現在のTodoアイテム数は0 6 | console.log(todoListModel.getTotalCount()); // => 0 7 | // Todoリストが変更されたら呼ばれるイベントリスナーを登録する 8 | todoListModel.onChange(() => { 9 | console.log("TodoListの状態が変わりました"); 10 | }); 11 | // 新しいTodoアイテムを追加する 12 | // => `onChange`で登録したイベントリスナーが呼び出される 13 | todoListModel.addTodo(new TodoItemModel({ 14 | title: "新しいTodoアイテム", 15 | completed: false 16 | })); 17 | // Todoリストにアイテムが増える 18 | console.log(todoListModel.getTotalCount()); // => 1 19 | -------------------------------------------------------------------------------- /source/use-case/todoapp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todoapp", 3 | "author": "azu", 4 | "license": "MIT", 5 | "private": true, 6 | "version": "1.0.0", 7 | "description": "TODO MVC like in ES2015", 8 | "main": "index.js", 9 | "type": "module", 10 | "directories": { 11 | "test": "test" 12 | }, 13 | "scripts": { 14 | "test": "node --test final/final/test/*.js", 15 | "e2e": "start-server-and-test start http://localhost:3000/README.md cy:run", 16 | "start": "npx --yes @js-primer/local-server --port 3000", 17 | "cy:open": "cypress open", 18 | "cy:run": "cypress run --browser chrome" 19 | }, 20 | "devDependencies": { 21 | "cypress": "^4.9.0", 22 | "start-server-and-test": "^2.0.3" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /source/use-case/nodecli/md-to-html/src/main-3.js: -------------------------------------------------------------------------------- 1 | import * as util from "node:util"; 2 | import * as fs from "node:fs/promises"; 3 | import { marked } from "marked"; 4 | 5 | const { 6 | values, 7 | positionals 8 | } = util.parseArgs({ 9 | allowPositionals: true, 10 | options: { 11 | gfm: { 12 | type: "boolean", 13 | default: false, 14 | } 15 | } 16 | }); 17 | const filePath = positionals[0]; 18 | fs.readFile(filePath, { encoding: "utf8" }).then(file => { 19 | const html = marked.parse(file, { 20 | // gfmフラグのパース結果をオプションとして渡す 21 | gfm: values.gfm 22 | }); 23 | console.log(html); 24 | }).catch(err => { 25 | console.error(err.message); 26 | process.exit(1); 27 | }); 28 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: deploy 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | permissions: 9 | contents: write 10 | jobs: 11 | deploy: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 15 | - name: Setup Node 16 | uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 17 | with: 18 | node-version: 22 19 | - run: npm install 20 | - run: npm run build 21 | - name: Deploy 22 | uses: peaceiris/actions-gh-pages@373f7f263a76c20808c831209c920827a82a2847 # v3.9.3 23 | with: 24 | github_token: ${{ secrets.GITHUB_TOKEN }} 25 | publish_dir: ./_book 26 | -------------------------------------------------------------------------------- /tools/gitbook/copy-favicon.cjs: -------------------------------------------------------------------------------- 1 | // MIT © 2018 azu 2 | "use strict"; 3 | const path = require("path"); 4 | const fs = require("fs"); 5 | const rootDir = path.join(__dirname, "../../"); 6 | const iconSource = path.join(rootDir, "./source/gitbook/icons"); 7 | const copy = (source, dest) => { 8 | if (source && fs.existsSync(source) && fs.existsSync(dest)) { 9 | fs.unlinkSync(dest); 10 | fs.createReadStream(source).pipe(fs.createWriteStream(dest)); 11 | return; 12 | } 13 | throw new Error("not match"); 14 | }; 15 | copy(path.join(iconSource, "favicon.ico"), path.join(rootDir, "_book/gitbook/images/favicon.ico")); 16 | copy(path.join(iconSource, "icon-152x152.png"), path.join(rootDir, "_book/gitbook/images/apple-touch-icon-precomposed-152.png")); 17 | -------------------------------------------------------------------------------- /source/use-case/todoapp/cypress/integration/form-event/prevent-event/prevent-event-spec.js: -------------------------------------------------------------------------------- 1 | const addNewTodo = require("../../../helper/todo-helper").addNewTodo; 2 | const URL = "/form-event/prevent-event"; 3 | const visitWithConsole = require("../../../helper/visit-with-console").visitWithConsole; 4 | describe(URL, function() { 5 | it("入力欄を埋めて送信するとコンソールログに表示される", function() { 6 | visitWithConsole(URL).then(({ logSpy }) => { 7 | const inputText = "test"; 8 | addNewTodo(inputText).then(() => { 9 | const logCalls = logSpy.getCalls(); 10 | const lastLog = logCalls[logCalls.length - 1].args[0]; 11 | expect(lastLog).to.equal(`入力欄の値: ${inputText}`); 12 | }); 13 | }); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /source/basic/module/src/re-export-invalid.js: -------------------------------------------------------------------------------- 1 | // ./my-module.jsのすべての名前つきエクスポートを再エクスポートする 2 | export * from "./my-module.js"; 3 | // [ES2020] ./my-module.jsのすべての名前つきエクスポートを名前空間オブジェクトとして再エクスポートする 4 | export * as myNameSpace from "./my-module.js"; 5 | // ./my-module.jsの名前つきエクスポートを選んで再エクスポートする 6 | export { foo, bar } from "./my-module.js"; 7 | // ./my-module.jsの名前つきエクスポートにエイリアスをつけて再エクスポートする 8 | export { foo as myModuleFoo, bar as myModuleBar } from "./my-module.js"; 9 | // ./my-module.jsのデフォルトエクスポートをデフォルトエクスポートとして再エクスポートする 10 | export { default } from "./my-module.js"; 11 | // ./my-module.jsのデフォルトエクスポートを名前つきエクスポートとして再エクスポートする 12 | export { default as myModuleDefault } from "./my-module.js"; 13 | // ./my-module.jsの名前つきエクスポートをデフォルトエクスポートとして再エクスポートする 14 | export { foo as default } from "./my-module.js"; 15 | -------------------------------------------------------------------------------- /source/use-case/todoapp/cypress/support/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import "./commands"; 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | -------------------------------------------------------------------------------- /source/use-case/nodecli/md-to-html/src/main.js: -------------------------------------------------------------------------------- 1 | import * as util from "node:util"; 2 | import * as fs from "node:fs/promises"; 3 | import { marked } from "marked"; 4 | 5 | // コマンドライン引数からファイルパスとオプションを受け取る 6 | const { 7 | values, 8 | positionals 9 | } = util.parseArgs({ 10 | allowPositionals: true, 11 | options: { 12 | // gfmフラグを定義する 13 | gfm: { 14 | type: "boolean", 15 | default: false, 16 | } 17 | } 18 | }); 19 | const filePath = positionals[0]; 20 | fs.readFile(filePath, { encoding: "utf8" }).then(file => { 21 | const html = marked.parse(file, { 22 | // オプションの値を使用する 23 | gfm: values.gfm, 24 | }); 25 | console.log(html); 26 | }).catch(err => { 27 | console.error(err.message); 28 | process.exit(1); 29 | }); 30 | -------------------------------------------------------------------------------- /source/use-case/todoapp/final/more/src/model/TodoItemModel.js: -------------------------------------------------------------------------------- 1 | // ユニークなIDを管理する変数 2 | let todoIdx = 0; 3 | 4 | export class TodoItemModel { 5 | /** @type {number} TodoアイテムのID */ 6 | id; 7 | /** @type {string} Todoアイテムのタイトル */ 8 | title; 9 | /** @type {boolean} Todoアイテムが完了済みならばtrue、そうでない場合はfalse */ 10 | completed; 11 | /** 12 | * @param {{ title: string, completed: boolean }} 13 | */ 14 | constructor({ title, completed }) { 15 | // idは連番となり、それぞれのインスタンス毎に異なるものとする 16 | this.id = todoIdx++; 17 | this.title = title; 18 | this.completed = completed; 19 | } 20 | 21 | /** 22 | * タイトルが空文字列の場合にtrueを返す 23 | * @returns {boolean} 24 | */ 25 | isEmptyTitle() { 26 | return this.title.length === 0; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /source/basic/error-try-catch/src/error-cause/index.js: -------------------------------------------------------------------------------- 1 | // 数値の文字列を受け取り数値を返す関数 2 | // 'text' など数値にはならない文字列を渡された場合は例外を投げられる 3 | function safeParseInt(numStr) { 4 | const num = Number.parseInt(numStr, 10); 5 | if (Number.isNaN(num)) { 6 | throw new Error(`${numStr} is not a numeric`); 7 | } 8 | return num; 9 | } 10 | 11 | // 数字の文字列を二つ受け取り、合計を返す関数 12 | function sumNumStrings(a, b) { 13 | try { 14 | const aNumber = safeParseInt(a); 15 | const bNumber = safeParseInt(b); 16 | return aNumber + bNumber; 17 | } catch (e) { 18 | throw new Error("Failed to sum a and b", { cause: e }); 19 | } 20 | } 21 | 22 | try { 23 | // 数値にならない文字列 'string' を渡しているので例外が投げられる 24 | sumNumStrings("string", "2"); 25 | } catch (err) { 26 | console.error(err); 27 | } 28 | -------------------------------------------------------------------------------- /source/use-case/todoapp/final/final/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Todo App 6 | 7 | 8 | 9 |
10 |
11 | 18 |
19 |
20 |
21 | Todoアイテム数: 0 22 |
23 |
24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /source/use-case/todoapp/final/more/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Todo App 6 | 7 | 8 | 9 |
10 |
11 | 18 |
19 |
20 |
21 | Todoアイテム数: 0 22 |
23 |
24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /textlint/textlint-rule-inline-code-denylist/index.js: -------------------------------------------------------------------------------- 1 | module.exports = (context, options = {}) => { 2 | const { Syntax, RuleError, report, getSource } = context; 3 | const denylist = options.denylist ? options.denylist : []; 4 | return { 5 | [Syntax.Code](node) { 6 | const value = node.value; 7 | const match = denylist.find(item => { 8 | return item === value || value.includes(`(${item}`) || value.includes(`${item}.`) || value.includes(`${item} =`); 9 | }); 10 | if (match) { 11 | const rawString = getSource(node); 12 | report(node, new RuleError(`インラインコード: ${rawString} の利用を禁止しています。 13 | 14 | 詳細: https://github.com/asciidwango/js-primer/issues/804`)); 15 | } 16 | } 17 | }; 18 | }; 19 | -------------------------------------------------------------------------------- /source/use-case/nodecli/md-to-html/src/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodecli", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "nodecli", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "marked": "^15.0.12" 13 | } 14 | }, 15 | "node_modules/marked": { 16 | "version": "15.0.12", 17 | "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz", 18 | "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", 19 | "license": "MIT", 20 | "bin": { 21 | "marked": "bin/marked.js" 22 | }, 23 | "engines": { 24 | "node": ">= 18" 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /source/use-case/todoapp/final/create-view/src/view/TodoItemView.sample.js: -------------------------------------------------------------------------------- 1 | import { render } from "./html-util.js"; 2 | import { TodoItemModel } from "../model/TodoItemModel.js"; 3 | import { TodoItemView } from "./TodoItemView.js"; 4 | 5 | // TodoItemViewをインスタンス化 6 | const todoItemView = new TodoItemView(); 7 | // 対応するTodoItemModelを作成する 8 | const todoItemModel = new TodoItemModel({ 9 | title: "あたらしいTodo", 10 | completed: false 11 | }); 12 | // TodoItemModelからHTML要素を作成する 13 | const todoItemElement = todoItemView.createElement(todoItemModel, { 14 | onUpdateTodo: () => { 15 | console.log("チェックボックスが更新されたときに呼ばれるリスナー関数"); 16 | }, 17 | onDeleteTodo: () => { 18 | console.log("削除ボタンがクリックされたときに呼ばれるリスナー関数"); 19 | } 20 | }); 21 | render(todoItemElement, document.body); //
  • 要素をdocument.bodyへレンダリング 22 | -------------------------------------------------------------------------------- /source/use-case/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | author: azu 3 | description: "第二部では第一部の基本文法で学んだことを応用し、具体的なユースケースを中心に紹介します" 4 | sponsors: [] 5 | --- 6 | 7 | # 第二部: ユースケース {#use-case} 8 | 9 | 第一部の基本文法で学んだことを応用し、具体的なユースケースを元に学んでいきます。 10 | 11 | ## 目次 {#summary} 12 | 13 | ### [アプリケーション開発の準備](./setup-local-env/README.md) {#setup-local-env} 14 | 15 | アプリケーション開発のためにNode.jsとnpmのインストールなどの準備方法を紹介します。 16 | 17 | ### [Ajax通信](./ajaxapp/README.md) {#ajaxapp} 18 | 19 | ウェブブラウザ上でAjax通信をするユースケースとして、GitHubのユーザーIDからプロフィール情報を取得するアプリケーションを作成しながら、非同期処理について紹介します。 20 | 21 | ### [Node.jsでCLIアプリ](./nodecli/README.md) {#nodecli} 22 | 23 | Node.jsでCLI(コマンドラインインターフェース)アプリケーションを開発する例として、MarkdownをHTMLに変換するツールを作成していきます。また、Node.jsやnpmの使い方を紹介します。 24 | 25 | ### [Todoアプリ](./todoapp/README.md) {#todoapp} 26 | 27 | ブラウザで動作するウェブアプリケーションの例としてTodoアプリを作成しながら、モジュールを使ったコード管理について紹介します。 28 | -------------------------------------------------------------------------------- /source/use-case/nodecli/refactor-and-unittest/src/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodecli", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "nodecli", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "marked": "^15.0.12" 13 | } 14 | }, 15 | "node_modules/marked": { 16 | "version": "15.0.12", 17 | "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz", 18 | "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", 19 | "license": "MIT", 20 | "bin": { 21 | "marked": "bin/marked.js" 22 | }, 23 | "engines": { 24 | "node": ">= 18" 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /source/basic/class/example/Square.js: -------------------------------------------------------------------------------- 1 | class Rectangle { 2 | constructor(height, width) { 3 | this.height = height; 4 | this.width = width; 5 | } 6 | 7 | getArea() { 8 | return this.height * this.width; 9 | } 10 | } 11 | 12 | // classはhoistingしないため、かならずRectangleの後に書かないといけない 13 | class Square extends Rectangle { 14 | constructor(length) { 15 | super(length, length); 16 | } 17 | } 18 | 19 | /** 20 | * Note: 21 | * Squareは次の性質を満たさないといけない 22 | * 23 | * - Immutable 24 | * - https://softwareengineering.stackexchange.com/questions/238176/why-would-square-inheriting-from-rectangle-be-problematic-if-we-override-the-set 25 | * - setした場合は、Squareの変更はRectangleとしての性質を満たないことがある 26 | * - 性質として継承できても、機能としての継承は別という話 27 | */ 28 | const square = new Square(3); 29 | console.log(square.getArea()); // => 9 30 | -------------------------------------------------------------------------------- /source/use-case/todoapp/event-model/event-emitter/src/model/TodoListModel.example.js: -------------------------------------------------------------------------------- 1 | import assert from "node:assert"; 2 | //! [main] 3 | import { TodoItemModel } from "./TodoItemModel.js"; 4 | import { TodoListModel } from "./TodoListModel.js"; 5 | // 新しいTodoリストを作成する 6 | const todoListModel = new TodoListModel(); 7 | // 現在のTodoアイテム数は0 8 | console.log(todoListModel.getTotalCount()); // => 0 9 | // Todoリストが変更されたら呼ばれるイベントリスナーを登録する 10 | todoListModel.onChange(() => { 11 | console.log("TodoListの状態が変わりました"); 12 | }); 13 | // 新しいTodoアイテムを追加する 14 | // => `onChange`で登録したイベントリスナーが呼び出される 15 | todoListModel.addTodo(new TodoItemModel({ 16 | title: "新しいTodoアイテム", 17 | completed: false 18 | })); 19 | // Todoリストにアイテムが増える 20 | console.log(todoListModel.getTotalCount()); // => 1 21 | //! [main] 22 | assert.strictEqual(todoListModel.getTotalCount(), 1); 23 | -------------------------------------------------------------------------------- /source/use-case/todoapp/cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | // / 2 | // *********************************************************** 3 | // This example plugins/index.js can be used to load plugins 4 | // 5 | // You can change the location of this file or turn off loading 6 | // the plugins file with the 'pluginsFile' configuration option. 7 | // 8 | // You can read more here: 9 | // https://on.cypress.io/plugins-guide 10 | // *********************************************************** 11 | 12 | // This function is called when a project is opened or re-opened (e.g. due to 13 | // the project's config changing) 14 | 15 | /** 16 | * @type {Cypress.PluginConfig} 17 | */ 18 | module.exports = (on, config) => { 19 | // `on` is used to hook into various events Cypress emits 20 | // `config` is the resolved Cypress config 21 | }; 22 | -------------------------------------------------------------------------------- /source/basic/condition/test/swtich/switch-example-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import strictEval from "strict-eval"; 3 | import fs from "fs"; 4 | import path from "path"; 5 | import url from "node:url"; 6 | import { describe, it } from "node:test"; 7 | const __filename__ = url.fileURLToPath(import.meta.url); 8 | const __dirname = path.dirname(__filename__); 9 | 10 | const Code = fs.readFileSync(path.join(__dirname, "../../src/switch/switch-example.js"), "utf-8"); 11 | describe("switch-example", function() { 12 | it("should output \"ECMAScript 2015\"", function() { 13 | const expectedMessage = "ECMAScript 2015"; 14 | const console = { 15 | log(message) { 16 | assert.equal(message, expectedMessage); 17 | } 18 | }; 19 | strictEval(Code, { 20 | console 21 | }); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /source/use-case/todoapp/final/final/src/model/TodoListModel.example.js: -------------------------------------------------------------------------------- 1 | import { TodoItemModel } from "./TodoItemModel.js"; 2 | import { TodoListModel } from "./TodoListModel.js"; 3 | // 新しいTodoリストを作成する 4 | const todoListModel = new TodoListModel(); 5 | // 現在のTodoアイテム数は0 6 | console.log(todoListModel.getTotalCount()); // => 0 7 | // Todoリストが変更されたら呼ばれるイベントリスナーを登録する 8 | todoListModel.onChange(() => { 9 | console.log("TodoListの状態が変わりました"); 10 | }); 11 | // 新しいTodoアイテムを追加する 12 | // => `onChange`で登録したイベントリスナーが呼び出される 13 | const todoItemModel = new TodoItemModel({ 14 | title: "新しいTodoアイテム", 15 | completed: false 16 | }); 17 | todoListModel.addTodo(todoItemModel); 18 | // Todoリストにアイテムが増える 19 | console.log(todoListModel.getTotalCount()); // => 1 20 | // アイテムを削除する 21 | todoListModel.deleteTodo({ 22 | id: todoItemModel.id 23 | }); 24 | console.log(todoListModel.getTotalCount()); // => 0 25 | -------------------------------------------------------------------------------- /source/use-case/todoapp/final/create-view/src/model/TodoListModel.example.js: -------------------------------------------------------------------------------- 1 | import { TodoItemModel } from "./TodoItemModel.js"; 2 | import { TodoListModel } from "./TodoListModel.js"; 3 | // 新しいTodoリストを作成する 4 | const todoListModel = new TodoListModel(); 5 | // 現在のTodoアイテム数は0 6 | console.log(todoListModel.getTotalCount()); // => 0 7 | // Todoリストが変更されたら呼ばれるイベントリスナーを登録する 8 | todoListModel.onChange(() => { 9 | console.log("TodoListの状態が変わりました"); 10 | }); 11 | // 新しいTodoアイテムを追加する 12 | // => `onChange`で登録したイベントリスナーが呼び出される 13 | const todoItemModel = new TodoItemModel({ 14 | title: "新しいTodoアイテム", 15 | completed: false 16 | }); 17 | todoListModel.addTodo(todoItemModel); 18 | // Todoリストにアイテムが増える 19 | console.log(todoListModel.getTotalCount()); // => 1 20 | // アイテムを削除する 21 | todoListModel.deleteTodo({ 22 | id: todoItemModel.id 23 | }); 24 | console.log(todoListModel.getTotalCount()); // => 0 25 | -------------------------------------------------------------------------------- /tools/update-summary.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | declare projectDir=$(git rev-parse --show-toplevel); 4 | # 基本文法 5 | node "${projectDir}/tools/generate-summary.js" --index "${projectDir}/source/basic/README.md" --pattern "**/basic/**/README.md" 6 | # ユースケース 7 | node "${projectDir}/tools/generate-summary.js" --index "${projectDir}/source/use-case/README.md" --pattern "**/use-case/*/README.md" 8 | # ajaxapp 9 | node "${projectDir}/tools/generate-summary.js" --index "${projectDir}/source/use-case/ajaxapp/README.md" --pattern "**/use-case/ajaxapp/**/README.md" 10 | # nodecli 11 | node "${projectDir}/tools/generate-summary.js" --index "${projectDir}/source/use-case/nodecli/README.md" --pattern "**/use-case/nodecli/**/README.md" 12 | # todoapp 13 | node "${projectDir}/tools/generate-summary.js" --index "${projectDir}/source/use-case/todoapp/README.md" --pattern "**/use-case/todoapp/**/README.md" 14 | -------------------------------------------------------------------------------- /source/basic/iterator-generator/examples/basic/iterator-example-test.js: -------------------------------------------------------------------------------- 1 | import { describe, it } from "node:test"; 2 | import assert from "node:assert"; 3 | import strictEval from "strict-eval"; 4 | import fs from "node:fs"; 5 | import path from "node:path"; 6 | import url from "node:url"; 7 | 8 | const __filename = url.fileURLToPath(import.meta.url); 9 | const __dirname = path.dirname(__filename); 10 | const Code = fs.readFileSync(path.join(__dirname, "./iterator.example.js"), "utf-8"); 11 | 12 | describe("iterator-example", () => { 13 | it("イテレータから最初の2つの値を取得できる", () => { 14 | const actualLogs = []; 15 | const console = { 16 | log(message) { 17 | actualLogs.push(message); 18 | } 19 | }; 20 | strictEval(Code, { 21 | console 22 | }); 23 | assert.deepEqual(actualLogs, [1, 2]); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /source/use-case/nodecli/refactor-and-unittest/src/main.js: -------------------------------------------------------------------------------- 1 | import * as util from "node:util"; 2 | import * as fs from "node:fs/promises"; 3 | // md2htmlモジュールからmd2html関数をインポートする 4 | import { md2html } from "./md2html.js"; 5 | 6 | // コマンドライン引数からファイルパスとオプション/フラグを受け取る 7 | const { 8 | values, 9 | positionals 10 | } = util.parseArgs({ 11 | allowPositionals: true, 12 | options: { 13 | // gfmフラグを定義する 14 | gfm: { 15 | type: "boolean", 16 | default: false, 17 | } 18 | } 19 | }); 20 | const filePath = positionals[0]; 21 | fs.readFile(filePath, { encoding: "utf8" }).then(file => { 22 | // md2htmlモジュールを使ってHTMLに変換する 23 | const html = md2html(file, { 24 | // gfmフラグのパース結果をオプションとして渡す 25 | gfm: values.gfm 26 | }); 27 | console.log(html); 28 | }).catch(err => { 29 | console.error(err.message); 30 | process.exit(1); 31 | }); 32 | -------------------------------------------------------------------------------- /source/use-case/todoapp/update-delete/delete-feature/src/model/TodoListModel.example.js: -------------------------------------------------------------------------------- 1 | import { TodoItemModel } from "./TodoItemModel.js"; 2 | import { TodoListModel } from "./TodoListModel.js"; 3 | // 新しいTodoリストを作成する 4 | const todoListModel = new TodoListModel(); 5 | // 現在のTodoアイテム数は0 6 | console.log(todoListModel.getTotalCount()); // => 0 7 | // Todoリストが変更されたら呼ばれるイベントリスナーを登録する 8 | todoListModel.onChange(() => { 9 | console.log("TodoListの状態が変わりました"); 10 | }); 11 | // 新しいTodoアイテムを追加する 12 | // => `onChange`で登録したイベントリスナーが呼び出される 13 | const todoItemModel = new TodoItemModel({ 14 | title: "新しいTodoアイテム", 15 | completed: false 16 | }); 17 | todoListModel.addTodo(todoItemModel); 18 | // Todoリストにアイテムが増える 19 | console.log(todoListModel.getTotalCount()); // => 1 20 | // アイテムを削除する 21 | todoListModel.deleteTodo({ 22 | id: todoItemModel.id 23 | }); 24 | console.log(todoListModel.getTotalCount()); // => 0 25 | -------------------------------------------------------------------------------- /source/basic/loop/test/do-while/do-while-example-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import strictEval from "strict-eval"; 3 | import fs from "fs"; 4 | import url from "node:url"; 5 | import path from "node:path"; 6 | import { describe, it } from "node:test"; 7 | 8 | const __filename__ = url.fileURLToPath(import.meta.url); 9 | const __dirname = path.dirname(__filename__); 10 | 11 | const Code = fs.readFileSync(path.join(__dirname, "../../src/do-while/do-while-example.js"), "utf-8"); 12 | describe("do-while", function() { 13 | it("最初の条件を関係なく一度実行される", function() { 14 | const actualLogs = []; 15 | const console = { 16 | log(message) { 17 | actualLogs.push(message); 18 | } 19 | }; 20 | strictEval(Code, { 21 | console 22 | }); 23 | assert.deepEqual(actualLogs, [ 24 | 1000 25 | ]); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /source/use-case/nodecli/refactor-and-unittest/src/main-last.js: -------------------------------------------------------------------------------- 1 | import * as util from "node:util"; 2 | import * as fs from "node:fs/promises"; 3 | // md2htmlモジュールからmd2html関数をインポートする 4 | import { md2html } from "./md2html.js"; 5 | 6 | // コマンドライン引数からファイルパスとオプション/フラグを受け取る 7 | const { 8 | values, 9 | positionals 10 | } = util.parseArgs({ 11 | allowPositionals: true, 12 | options: { 13 | // gfmフラグを定義する 14 | gfm: { 15 | type: "boolean", 16 | default: false, 17 | } 18 | } 19 | }); 20 | 21 | const filePath = positionals[0]; 22 | fs.readFile(filePath, { encoding: "utf8" }).then(file => { 23 | // md2htmlモジュールを使ってHTMLに変換する 24 | const html = md2html(file, { 25 | // gfmフラグのパース結果をオプションとして渡す 26 | gfm: values.gfm 27 | }); 28 | console.log(html); 29 | }).catch(err => { 30 | console.error(err.message); 31 | process.exit(1); 32 | }); 33 | -------------------------------------------------------------------------------- /source/use-case/todoapp/final/create-view/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Todo App 6 | 10 | 11 | 12 |
    13 |
    14 | 21 |
    22 |
    23 | 24 |
    25 |
    26 | Todoアイテム数: 0 27 |
    28 |
    29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /source/use-case/todoapp/event-model/event-emitter/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Todo App 6 | 10 | 11 | 12 |
    13 |
    14 | 21 |
    22 |
    23 | 24 |
    25 |
    26 | Todoアイテム数: 0 27 |
    28 |
    29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /source/use-case/todoapp/form-event/add-todo-item/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Todo App 6 | 10 | 11 | 12 |
    13 |
    14 | 21 |
    22 |
    23 | 24 |
    25 |
    26 | Todoアイテム数: 0 27 |
    28 |
    29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /source/use-case/todoapp/form-event/prevent-event/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Todo App 6 | 10 | 11 | 12 |
    13 |
    14 | 21 |
    22 |
    23 | 24 |
    25 |
    26 | Todoアイテム数: 0 27 |
    28 |
    29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /source/use-case/todoapp/update-delete/add-checkbox/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Todo App 6 | 10 | 11 | 12 |
    13 |
    14 | 21 |
    22 |
    23 | 24 |
    25 |
    26 | Todoアイテム数: 0 27 |
    28 |
    29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /source/basic/iterator-generator/examples/protocol/manual-iteration-example-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import strictEval from "strict-eval"; 3 | import fs from "fs"; 4 | import path from "path"; 5 | import url from "node:url"; 6 | import { describe, it } from "node:test"; 7 | 8 | const __filename__ = url.fileURLToPath(import.meta.url); 9 | const __dirname = path.dirname(__filename__); 10 | const Code = fs.readFileSync(path.join(__dirname, "./manual-iteration.example.js"), "utf-8"); 11 | 12 | describe("manual-iteration", function() { 13 | it("手動でIteratorを操作して値を取得できる", function() { 14 | const actualLogs = []; 15 | const consoleMock = { 16 | log(message) { 17 | actualLogs.push(message); 18 | } 19 | }; 20 | strictEval(Code, { 21 | console: consoleMock 22 | }); 23 | assert.deepStrictEqual(actualLogs, [1, 2, 3]); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /source/use-case/todoapp/update-delete/delete-feature/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Todo App 6 | 10 | 11 | 12 |
    13 |
    14 | 21 |
    22 |
    23 | 24 |
    25 |
    26 | Todoアイテム数: 0 27 |
    28 |
    29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /source/use-case/todoapp/update-delete/update-feature/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Todo App 6 | 10 | 11 | 12 |
    13 |
    14 | 21 |
    22 |
    23 | 24 |
    25 |
    26 | Todoアイテム数: 0 27 |
    28 |
    29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /meetings/2015-12-17/README.md: -------------------------------------------------------------------------------- 1 | # 2015-12-17 Meeting Notes 2 | 3 | - [@azu](https://github.com/azu) 4 | - [@kahei](https://github.com/kahei) 5 | - [@vvakame](https://github.com/vvakame) 6 | - [@lacolaco](https://github.com/lacolaco) 7 | 8 | ----- 9 | 10 | 顔合わせ 11 | 12 | - IT企業に新しく入った人にこれ読んでおいてで渡せるようなJavaScript入門書 13 | - 完全なプログラミング初心者向けではない 14 | 15 | ## 公開方法 16 | 17 | - @kahei: 電子書籍版は同時に販売するのは可能 18 | - @azu: HTML版をそのままウェブで公開したい 19 | - @azu: ウェブで公開することパーマネントリンクを貼れることが重要 20 | - GitHubの公開リポジトリを使うとか(gh-pages) 21 | - @azu: 販売後も継続的にメンテナンスしたい 22 | 23 | 24 | ### Conclusion 25 | 26 | 特に決定せず 27 | 28 | ## 文書フォーマットについて 29 | 30 | - Markdown? Re:View? 31 | - Markdownそのものだと機能不足 32 | - コードを外部ファイルから読み込む機能などの拡張は必要 33 | - Markdown -> Sphinx(索引) -> LaTex という方法もある 34 | - 著者が索引のアノテーションを付けるのもありなのでは? 35 | - 著者が書きやすいフォーマットと書籍の組版フォーマットの二重管理問題について 36 | 37 | 38 | ### Conclusion 39 | 40 | 特に決定せず 41 | 42 | 43 | ## 次回 44 | 45 | - 2016年1月の後半ぐらいにもう一度ミーティング 46 | -------------------------------------------------------------------------------- /source/use-case/ajaxapp/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | author: laco 3 | description: "ウェブブラウザ上でAjax通信をするユースケースとして、GitHubのユーザーIDからプロフィール情報を取得するアプリケーションを作成しながら、非同期処理について紹介します。" 4 | sponsors: [] 5 | --- 6 | 7 | # ユースケース: Ajax通信 {#usecase-ajax} 8 | 9 | ここではウェブブラウザ上でAjax通信をするユースケースとして、GitHubのユーザーIDからプロフィール情報を取得するアプリケーションを作成します。 10 | 11 | 作成するアプリケーションは次の要件を満たすものとします。 12 | 13 | - GitHubのユーザーIDをテキストボックスに入力できる 14 | - 入力されたユーザーIDを元にGitHubからユーザー情報を取得する 15 | - 取得したユーザー情報をアプリケーション上で表示する 16 | 17 | ## 目次 {#summary} 18 | 19 | ### [エントリーポイント](./entrypoint/README.md) {#entrypoint} 20 | 21 | アプリケーションの中で一番最初に呼び出されるエントリーポイントを作成します。 22 | 23 | ### [HTTP通信](./http/README.md) {#http-communication} 24 | 25 | ウェブ標準のFetch APIを使ってHTTP通信を行い、GitHubのAPIを呼び出します。 26 | 27 | ### [データを表示する](./display/README.md) {#display} 28 | 29 | Fetch APIを使って取得したデータを元にHTMLを組み立ててブラウザ上で表示します。 30 | 31 | ### [Promiseを活用する](./promise/README.md) {#promise} 32 | 33 | Promiseを活用し、ソースコードの整理とエラーハンドリングを行います。 34 | -------------------------------------------------------------------------------- /source/basic/loop/test/for-of/for-of-array-example-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import strictEval from "strict-eval"; 3 | import fs from "fs"; 4 | import path from "path"; 5 | import url from "node:url"; 6 | import { describe, it } from "node:test"; 7 | 8 | const __filename__ = url.fileURLToPath(import.meta.url); 9 | const __dirname = path.dirname(__filename__); 10 | const Code = fs.readFileSync(path.join(__dirname, "../../src/for-of/for-of-array-example.js"), "utf-8"); 11 | describe("for-of-array", function() { 12 | it("配列の値が列挙される", function() { 13 | const actualLogs = []; 14 | const console = { 15 | log(message) { 16 | actualLogs.push(message); 17 | } 18 | }; 19 | strictEval(Code, { 20 | console 21 | }); 22 | assert.deepEqual(actualLogs, [ 23 | 1, 24 | 2, 25 | 3 26 | ]); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /source/intro/authors/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | author: laco 3 | description: "JavaScript Primerの著者紹介" 4 | sponsors: [] 5 | --- 6 | 7 | # 著者紹介 {#authors} 8 | 9 | ## azu {#author-azu} 10 | 11 | azu 12 | 13 | ISO/IEC JTC 1/SC 22/ECMAScript Ad Hoc委員会 エキスパートでECMAScript、JSONの仕様に関わる。 14 | 2011年にJSer.infoを立ち上げ、継続的にJavaScriptの情報を発信している。 15 | ライフワークとしてオープンソースへのコントリビューションをしている。 16 | 17 | - X: https://x.com/azu_re 18 | - GitHub: https://github.com/azu 19 | 20 | ## Suguru Inatomi {#author-suguru-inatomi} 21 | 22 | lacolaco 23 | 24 | 長崎生まれ福岡育ち。2016年よりAngular日本ユーザー会の代表を務める。 25 | 2018年に日本で一人目のGoogle Developers Expert for Angularに認定される。 26 | 日々の仕事の傍ら、AngularをはじめとするOSSへのコントリビューションや翻訳、登壇、イベントの主催などの活動を続けている。 27 | 28 | - X: https://x.com/laco2net 29 | - GitHub: https://github.com/lacolaco 30 | -------------------------------------------------------------------------------- /source/basic/loop/test/for-in/for-in-array-bug-example-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import strictEval from "strict-eval"; 3 | import fs from "fs"; 4 | import url from "node:url"; 5 | import path from "node:path"; 6 | import { describe, it } from "node:test"; 7 | 8 | const __filename__ = url.fileURLToPath(import.meta.url); 9 | const __dirname = path.dirname(__filename__); 10 | 11 | const Code = fs.readFileSync(path.join(__dirname, "../../src/for-in/for-in-array-bug-example.js"), "utf-8"); 12 | describe("for-in-array", function() { 13 | it("配列の添字が列挙される", function() { 14 | const actualLogs = []; 15 | const console = { 16 | log(message) { 17 | actualLogs.push(message); 18 | } 19 | }; 20 | strictEval(Code, { 21 | console 22 | }); 23 | // 文字列であるため 24 | assert.deepEqual(actualLogs, [ 25 | "001" 26 | ]); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /source/use-case/ajaxapp/entrypoint/screenshot.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | declare projectDir=$(git rev-parse --show-toplevel); 4 | declare currentDir=$(pwd) 5 | declare screenshotDevTools="${projectDir}/tools/applescript/lib/src/screenshot-dev-tools.js"; 6 | declare screenshot="${projectDir}/tools/applescript/lib/src/screenshot.js"; 7 | declare launchFirefox="${projectDir}/tools/applescript/lib/src/launch-firefox.js"; 8 | declare screenshotOnly="${projectDir}/tools/applescript/lib/src/screenshot-only.js"; 9 | mkdir -p "${currentDir}/img/" 10 | echo "local server 起動" 11 | npx --yes -q @js-primer/local-server src/ & 12 | serverPID=$! 13 | echo "screenshotを撮影" 14 | npx --yes -q wait-on http://localhost:3000 \ 15 | && node "${screenshotDevTools}" --url "http://localhost:3000/" --output "${currentDir}/img/fig-1.png" 16 | # server 終了 17 | function finish { 18 | echo "Shutting down the server..." 19 | pkill js-primer-local-server 20 | } 21 | trap finish INT TERM EXIT 22 | -------------------------------------------------------------------------------- /source/basic/loop/test/for-of/for-of-string-example-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import strictEval from "strict-eval"; 3 | import fs from "fs"; 4 | import url from "node:url"; 5 | import path from "node:path"; 6 | import { describe, it } from "node:test"; 7 | 8 | const __filename__ = url.fileURLToPath(import.meta.url); 9 | const __dirname = path.dirname(__filename__); 10 | 11 | const Code = fs.readFileSync(path.join(__dirname, "../../src/for-of/for-of-string-example.js"), "utf-8"); 12 | describe("for-of-string", function() { 13 | it("can handle サロゲートペア", function() { 14 | const actualLogs = []; 15 | const console = { 16 | log(message) { 17 | actualLogs.push(message); 18 | } 19 | }; 20 | strictEval(Code, { 21 | console 22 | }); 23 | assert.deepEqual(actualLogs, [ 24 | "𠮷", 25 | "野", 26 | "家" 27 | ]); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /source/use-case/todoapp/update-delete/update-feature/src/model/TodoListModel.example.js: -------------------------------------------------------------------------------- 1 | import { TodoItemModel } from "./TodoItemModel.js"; 2 | import { TodoListModel } from "./TodoListModel.js"; 3 | // 新しいTodoリストを作成する 4 | const todoListModel = new TodoListModel(); 5 | // 現在のTodoアイテム数は0 6 | console.log(todoListModel.getTotalCount()); // => 0 7 | // Todoリストが変更されたら呼ばれるイベントリスナーを登録する 8 | todoListModel.onChange(() => { 9 | console.log("TodoListの状態が変わりました"); 10 | }); 11 | // 新しいTodoアイテムを追加する 12 | // => `onChange`で登録したイベントリスナーが呼び出される 13 | const todoItemModel = new TodoItemModel({ 14 | title: "新しいTodoアイテム", 15 | completed: false 16 | }); 17 | todoListModel.addTodo(todoItemModel); 18 | // Todoリストにアイテムが増える 19 | console.log(todoListModel.getTotalCount()); // => 1 20 | // 完了状態を更新する 21 | todoListModel.updateTodo({ 22 | id: todoItemModel.id, 23 | completed: true 24 | }); 25 | todoListModel.getTodoItems().forEach(item => { 26 | console.log(item.completed); // => true 27 | }); 28 | -------------------------------------------------------------------------------- /source/use-case/todoapp/cypress/support/commands.js: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add("login", (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This is will overwrite an existing command -- 25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 26 | -------------------------------------------------------------------------------- /source/use-case/todoapp/cypress/integration/app-structure/todo-html/todo-html-spec.js: -------------------------------------------------------------------------------- 1 | const URL = "/app-structure/todo-html"; 2 | const visitWithConsole = require("../../../helper/visit-with-console").visitWithConsole; 3 | describe(URL, function() { 4 | it(".todoappにスタイルが適応されている", function() { 5 | cy.visit(URL).then((win) => { 6 | const position = win.getComputedStyle(win.document.querySelector(".todoapp")).position; 7 | expect(position).to.equal("relative"); 8 | }); 9 | cy.get("#js-todo-count").should(count => { 10 | expect(count).to.contain("0"); 11 | }); 12 | }); 13 | it("ロードするとApp.jsのログが表示される", function() { 14 | visitWithConsole(URL).then(({ logSpy }) => { 15 | const log0 = logSpy.getCall(0).args[0]; 16 | const log1 = logSpy.getCall(1).args[0]; 17 | expect(log0).to.equal("App.js: loaded"); 18 | expect(log1).to.equal("App initialized"); 19 | }); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /source/basic/condition/test/swtich/miss-case-example-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import strictEval from "strict-eval"; 3 | import fs from "fs"; 4 | import path from "path"; 5 | import url from "node:url"; 6 | import { describe, it } from "node:test"; 7 | const __filename__ = url.fileURLToPath(import.meta.url); 8 | const __dirname = path.dirname(__filename__); 9 | 10 | const Code = fs.readFileSync(path.join(__dirname, "../../src/switch/miss-case-example.js"), "utf-8"); 11 | describe("switch-example", function() { 12 | it("should output \"ECMAScript 2015\"", function() { 13 | const actualLogs = []; 14 | const console = { 15 | log(message) { 16 | actualLogs.push(message); 17 | } 18 | }; 19 | strictEval(Code, { 20 | console 21 | }); 22 | assert.deepEqual(actualLogs, [ 23 | "ECMAScript 2015", 24 | "ECMAScript 2016", 25 | "しらないバージョンです" 26 | ]); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /source/basic/loop/test/for-in/for-in-object-example-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import strictEval from "strict-eval"; 3 | import fs from "fs"; 4 | import url from "node:url"; 5 | import path from "node:path"; 6 | import { describe, it } from "node:test"; 7 | 8 | const __filename__ = url.fileURLToPath(import.meta.url); 9 | const __dirname = path.dirname(__filename__); 10 | const Code = fs.readFileSync(path.join(__dirname, "../../src/for-in/for-in-object-example.js"), "utf-8"); 11 | describe("for-in-object", function() { 12 | it("オブジェクトのkey:valueが列挙される", function() { 13 | const actualLogs = []; 14 | const console = { 15 | log(message) { 16 | actualLogs.push(message); 17 | } 18 | }; 19 | strictEval(Code, { 20 | console 21 | }); 22 | assert.deepEqual(actualLogs, [ 23 | "key:a, value:1", 24 | "key:b, value:2", 25 | "key:c, value:3", 26 | ]); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /source/basic/async/example/dummyFetch.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 1000ミリ秒未満のランダムなタイミングでレスポンスを擬似的にデータ取得する関数 3 | * 指定した`path`にデータがあるなら`callback(null, レスポンス)`を呼ぶ 4 | * データがない場合はNOT FOUNDとなり`callback(エラー)`を呼ぶ 5 | */ 6 | function dummyFetch(path, callback) { 7 | setTimeout(() => { 8 | // /success を含むパスにはリソースがあるという設定 9 | if (path.startsWith("/success")) { 10 | callback(null, { body: `Response body of ${path}` }); 11 | } else { 12 | callback(new Error("NOT FOUND")); 13 | } 14 | }, 1000 * Math.random()); 15 | } 16 | 17 | dummyFetch("/success/data", (error, response) => { 18 | console.log(error, response); 19 | }); 20 | 21 | dummyFetch("/failure/data", (error, response) => { 22 | console.log(error, response); 23 | }); 24 | 25 | // nest 26 | dummyFetch("/success/data", (error, response) => { 27 | console.log(error, response); 28 | // nest 29 | dummyFetch("/failure/data", (error, response) => { 30 | console.log(error, response); 31 | }); 32 | 33 | }); 34 | -------------------------------------------------------------------------------- /.github/workflows/link-check.yml: -------------------------------------------------------------------------------- 1 | name: Link Check 2 | on: 3 | workflow_dispatch: 4 | schedule: 5 | - cron: '45 15 * * *' 6 | 7 | permissions: 8 | contents: read 9 | security-events: write 10 | 11 | jobs: 12 | test: 13 | runs-on: ubuntu-latest 14 | name: Link Check 15 | steps: 16 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 17 | - name: Setup Node.js 18 | uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 19 | with: 20 | node-version: 22 21 | - run: npm ci 22 | - run: npm run textlint-sarif 23 | - name: Upload SARIF file 24 | uses: github/codeql-action/upload-sarif@b8d3b6e8af63cde30bdc382c0bc28114f4346c88 # v2.28.1 25 | with: 26 | # Path to SARIF file relative to the root of the repository 27 | sarif_file: textlint.sarif 28 | # Optional category for the results 29 | # Used to differentiate multiple results for one commit 30 | category: textlint 31 | -------------------------------------------------------------------------------- /source/basic/iterator-generator/examples/basic/array-vs-iterator-example-test.js: -------------------------------------------------------------------------------- 1 | import { describe, it } from "node:test"; 2 | import assert from "node:assert"; 3 | import strictEval from "strict-eval"; 4 | import fs from "node:fs"; 5 | import path from "node:path"; 6 | import url from "node:url"; 7 | 8 | const __filename = url.fileURLToPath(import.meta.url); 9 | const __dirname = path.dirname(__filename); 10 | const Code = fs.readFileSync(path.join(__dirname, "./array-vs-iterator.example.js"), "utf-8"); 11 | 12 | describe("array-vs-iterator", () => { 13 | it("配列のサイズと最初の5つの要素が正しい", () => { 14 | const actualLogs = []; 15 | const consoleMock = { 16 | log(message) { 17 | actualLogs.push(message); 18 | } 19 | }; 20 | strictEval(Code, { 21 | console: consoleMock 22 | }); 23 | assert.strictEqual(actualLogs[0], 5000); 24 | // 配列のRealmが異なるのでstrictEqualは使えない 25 | assert.deepEqual(actualLogs[1], [1, 2, 3, 4, 5]); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /source/basic/iterator-generator/examples/protocol/create-range.example.js: -------------------------------------------------------------------------------- 1 | // 範囲の数値を生成するIterable Iteratorの実装 2 | function createRange(start, end) { 3 | let current = start; 4 | return { 5 | // `current`が`end`以下の間、次の値を返し、`current`をインクリメントする 6 | // `end`を超えた場合は、doneをtrueにして終了 7 | next() { 8 | if (current <= end) { 9 | return { value: current++, done: false }; 10 | } else { 11 | return { value: undefined, done: true }; 12 | } 13 | }, 14 | // Iterableプロトコル: Symbol.iteratorメソッドを実装 15 | [Symbol.iterator]() { 16 | return this; 17 | } 18 | }; 19 | } 20 | 21 | // Iterable Iteratorを取得 22 | const range = createRange(1, 3); 23 | // Iteratorを取得 24 | const iterator = range[Symbol.iterator](); 25 | // Iteratorを使って、値を順番に取得 26 | console.log(iterator.next().value); // => 1 27 | console.log(iterator.next().value); // => 2 28 | console.log(iterator.next().value); // => 3 29 | console.log(iterator.next().value); // => undefined -------------------------------------------------------------------------------- /source/basic/loop/test/for-in/object-keys-for-each-example-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import strictEval from "strict-eval"; 3 | import fs from "node:fs"; 4 | import url from "node:url"; 5 | import path from "node:path"; 6 | import { describe, it } from "node:test"; 7 | 8 | const __filename__ = url.fileURLToPath(import.meta.url); 9 | const __dirname = path.dirname(__filename__); 10 | 11 | const Code = fs.readFileSync(path.join(__dirname, "../../src/for-in/object-keys-for-each-example.js"), "utf-8"); 12 | describe("object-keys-for-each", function() { 13 | it("オブジェクトのkey:valueが列挙される", function() { 14 | const actualLogs = []; 15 | const console = { 16 | log(message) { 17 | actualLogs.push(message); 18 | } 19 | }; 20 | strictEval(Code, { 21 | console 22 | }); 23 | assert.deepEqual(actualLogs, [ 24 | "key:a, value:1", 25 | "key:b, value:2", 26 | "key:c, value:3", 27 | ]); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /tools/applescript/src/screenshot-only.ts: -------------------------------------------------------------------------------- 1 | import { quitFirefox, screenshotFirefox, setFirefoxWindowBounds } from "../modules/firefox.js"; 2 | import * as path from "path"; 3 | import { wait } from "../modules/wait.js"; 4 | import meow from "meow"; 5 | 6 | 7 | const cli = meow(` 8 | Usage 9 | $ screenshot-only 10 | 11 | Options 12 | --output, -o output path of image 13 | 14 | Examples 15 | $ screenshot --output ./output.png 16 | `, { 17 | flags: { 18 | output: { 19 | type: "string", 20 | alias: "o", 21 | isRequired: true 22 | }, 23 | continue: { 24 | type: "boolean", 25 | isRequired: true 26 | } 27 | } 28 | }); 29 | const outputFilePath = path.resolve(process.cwd(), cli.flags.output); 30 | (async function() { 31 | await setFirefoxWindowBounds(); 32 | await wait(1000); 33 | await screenshotFirefox(outputFilePath); 34 | if (!cli.flags.continue) { 35 | await quitFirefox(); 36 | } 37 | })(); 38 | -------------------------------------------------------------------------------- /source/basic/error-try-catch/screenshot-manual.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | declare projectDir=$(git rev-parse --show-toplevel); 4 | declare currentDir=$(pwd) 5 | declare screenshotDevTools="${projectDir}/tools/applescript/lib/src/screenshot-dev-tools.js"; 6 | declare screenshot="${projectDir}/tools/applescript/lib/src/screenshot.js"; 7 | declare launchFirefox="${projectDir}/tools/applescript/lib/src/launch-firefox.js"; 8 | declare screenshotOnly="${projectDir}/tools/applescript/lib/src/screenshot-only.js"; 9 | # マニュアル操作が必要なもの 10 | # formの内容をコンソールに表示するスクショ 11 | mkdir -p "${currentDir}/img/" 12 | npx --yes -q @js-primer/local-server src/console 13 | npx --yes -q wait-on http://localhost:3000/ \ 14 | && node "${launchFirefox}" --devTools --url "http://localhost:3000" \ 15 | && read -p "コンソールのエラーを展開 -> Enter" \ 16 | && node "${screenshotOnly}" --output "${currentDir}/img/console.error.png" 17 | 18 | # server 終了 19 | function finish { 20 | echo "Shutting down the server..." 21 | pkill js-primer-local-server 22 | } 23 | trap finish INT KILL TERM EXIT 24 | -------------------------------------------------------------------------------- /source/use-case/ajaxapp/promise/screenshot-manual.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | declare projectDir=$(git rev-parse --show-toplevel); 4 | declare currentDir=$(pwd) 5 | declare screenshotDevTools="${projectDir}/tools/applescript/lib/src/screenshot-dev-tools.js"; 6 | declare screenshot="${projectDir}/tools/applescript/lib/src/screenshot.js"; 7 | declare launchFirefox="${projectDir}/tools/applescript/lib/src/launch-firefox.js"; 8 | declare screenshotOnly="${projectDir}/tools/applescript/lib/src/screenshot-only.js"; 9 | mkdir -p "${currentDir}/img/" 10 | echo "local server 起動" 11 | npx --yes -q @js-primer/local-server src/ & 12 | serverPID=$! 13 | echo "screenshotを撮影" 14 | npx --yes -q wait-on http://localhost:3000 \ 15 | && node "${launchFirefox}" --url "http://localhost:3000/" \ 16 | && read -p "ユーザー表示後のスクショ: ボタンをクリック -> Enter" \ 17 | && node "${screenshotOnly}" --output "${currentDir}/img/fig-1.png" \ 18 | 19 | # server 終了 20 | function finish { 21 | echo "Shutting down the server..." 22 | pkill js-primer-local-server 23 | } 24 | trap finish INT TERM EXIT 25 | -------------------------------------------------------------------------------- /.github/workflows/calibreapp-image-manual.yml: -------------------------------------------------------------------------------- 1 | name: Compress Images 2 | on: 3 | workflow_dispatch: 4 | permissions: 5 | contents: write 6 | pull-requests: write 7 | jobs: 8 | build: 9 | name: calibreapp/image-actions 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout Repo 13 | uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 14 | - name: Compress Images 15 | id: calibre 16 | uses: calibreapp/image-actions@737ceeaeed61e17b8d358358a303f1b8d177b779 # 1.1.0 17 | with: 18 | githubToken: ${{ secrets.GITHUB_TOKEN }} 19 | compressOnly: true 20 | - name: Create New Pull Request If Needed 21 | if: steps.calibre.outputs.markdown != '' 22 | uses: peter-evans/create-pull-request@18f7dc018cc2cd597073088f7c7591b9d1c02672 # v3.14.0 23 | with: 24 | title: Compressed Images Nightly 25 | branch-suffix: timestamp 26 | commit-message: Compressed Images 27 | body: ${{ steps.calibre.outputs.markdown }} 28 | -------------------------------------------------------------------------------- /tools/applescript/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "module": "ESNext", 5 | "moduleResolution": "NodeNext", 6 | "newLine": "LF", 7 | "outDir": "./lib/", 8 | "target": "es2015", 9 | "sourceMap": false, 10 | "declaration": false, 11 | "jsx": "preserve", 12 | "esModuleInterop": true, 13 | "lib": [ 14 | "es2017", 15 | "dom" 16 | ], 17 | /* Strict Type-Checking Options */ 18 | "strict": true, 19 | /* Additional Checks */ 20 | "noUnusedLocals": true, 21 | /* Report errors on unused locals. */ 22 | "noUnusedParameters": true, 23 | /* Report errors on unused parameters. */ 24 | "noImplicitReturns": true, 25 | /* Report error when not all code paths in function return a value. */ 26 | "noFallthroughCasesInSwitch": true, 27 | "skipDefaultLibCheck": true 28 | }, 29 | "include": [ 30 | "../../node_modules/@jxa/global-type/src/index.d.ts", 31 | "**/*" 32 | ], 33 | "exclude": [ 34 | ".git", 35 | "node_modules" 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /source/basic/introduction/img/javascript-ecmascript.xml: -------------------------------------------------------------------------------- 1 | tZXBjpswEIafhmMrsANLjoFNtlp1Va1yaG+VAwO4a3DqOCHp03cA45iykVJV9SHC/4w99v/ZjkfT+vyk2L56kTkIj/j52aOPHiHLIMbfTrgMQhSTQSgVzwcpuApb/guM6Bv1yHM4TBK1lELz/VTMZNNApicaU0q207RCimnVPSthJmwzJubqV57ryqhBtLwGPgEvK1M6Jg9DYMeyt1LJY2PqeYQWfRvCNRvnMhs9VCyXrSPRtUdTJaUevupzCqKzdrRtGLe5EbXrVtDoewaQIKR0GHRi4gjjqvu16cvoBw5C67GTFFyIVAqp+kC/uThGXe5ZxnWHO/Kxe9BKvoGTSCO6pFg0qXQtUArwU7AdiMQ6NmY3sgEblSoH5czj981WGOlQVOZ7N3acQGk4O5Lx4glkDVpdMMVEKQmHIebcBpExp72egtA37CrnAFiRmZNX2rmv7uOHAXAbBpnBWKcvq22m+F7fjyUgq1US3oHF2ulgKWSjnZRN34xuripZ/C8Ciwd/QoD68YyAfSZcAlb8FwLZY/zz+dsrvH7+vtn9+LIu/HD7AbEsohmWZ3ZiN7DgVvXU0qnv5oC7zIzEBC8b7GZoIKCedMZxfJdWJlDzPO/KJG3FNWyRblezxUcYtf4WQbcV/x1cf2AdmoU2I/QOx9vQluTj9OLYB87BFkThHFv099Swe30g+5jzJ0TXvwE= -------------------------------------------------------------------------------- /source/use-case/setup-local-env/screenshot.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | declare projectDir=$(git rev-parse --show-toplevel); 4 | declare currentDir=$(pwd) 5 | declare screenshotDevTools="${projectDir}/tools/applescript/lib/src/screenshot-dev-tools.js"; 6 | declare screenshot="${projectDir}/tools/applescript/lib/src/screenshot.js"; 7 | declare launchFirefox="${projectDir}/tools/applescript/lib/src/launch-firefox.js"; 8 | declare screenshotOnly="${projectDir}/tools/applescript/lib/src/screenshot-only.js"; 9 | # setup 10 | mkdir -p "${currentDir}/img/" 11 | echo "local server 起動" 12 | npx --yes -q @js-primer/local-server src/ & 13 | serverPID=$! 14 | # screenshot 15 | echo "screenshotを撮影" 16 | npx --yes -q wait-on http://localhost:3000 \ 17 | && node "${launchFirefox}" --devTools --url "http://localhost:3000/" \ 18 | && read -p "コンソールのスクショ: コンソールタブを開く -> Enter" \ 19 | && node "${screenshotOnly}" --output "${currentDir}/img/index.png" \ 20 | 21 | # server 終了 22 | function finish { 23 | echo "Shutting down the server..." 24 | pkill js-primer-local-server 25 | } 26 | trap finish INT TERM EXIT 27 | -------------------------------------------------------------------------------- /source/use-case/todoapp/final/create-view/src/view/TodoItemView.example.js: -------------------------------------------------------------------------------- 1 | import jsdom_global from "jsdom-global"; 2 | const cleanup = jsdom_global(); 3 | //! [main] 4 | import { TodoItemModel } from "../model/TodoItemModel.js"; 5 | import { TodoItemView } from "./TodoItemView.js"; 6 | 7 | // TodoItemViewをインスタンス化 8 | const todoItemView = new TodoItemView(); 9 | // 対応するTodoItemModelを作成する 10 | const todoItemModel = new TodoItemModel({ 11 | title: "あたらしいTodo", 12 | completed: false 13 | }); 14 | // TodoItemModelからHTML要素を作成する 15 | const todoItemElement = todoItemView.createElement(todoItemModel, { 16 | onUpdateTodo: () => { 17 | // チェックボックスが更新されたときに呼ばれるリスナー関数 18 | }, 19 | onDeleteTodo: () => { 20 | // 削除ボタンがクリックされたときに呼ばれるリスナー関数 21 | } 22 | }); 23 | console.log(todoItemElement); //
  • 要素が入る 24 | //! [main] 25 | // Test 26 | console.log(todoItemElement.innerHTML.includes("あたらしいTodo")); // => true 27 | console.log(!todoItemElement.innerHTML.includes("checked")); // => true 28 | console.log(todoItemElement.innerHTML.includes("delete")); // => true 29 | cleanup(); 30 | -------------------------------------------------------------------------------- /source/use-case/todoapp/final/final/src/view/TodoItemView.example.js: -------------------------------------------------------------------------------- 1 | import jsdom_global from "jsdom-global"; 2 | 3 | const cleanup = jsdom_global(); 4 | //! [main] 5 | import { TodoItemModel } from "../model/TodoItemModel.js"; 6 | import { TodoItemView } from "./TodoItemView.js"; 7 | 8 | // TodoItemViewをインスタンス化 9 | const todoItemView = new TodoItemView(); 10 | // 対応するTodoItemModelを作成する 11 | const todoItemModel = new TodoItemModel({ 12 | title: "あたらしいTodo", 13 | completed: false 14 | }); 15 | // TodoItemModelからHTML要素を作成する 16 | const todoItemElement = todoItemView.createElement(todoItemModel, { 17 | onUpdateTodo: () => { 18 | // チェックボックスが更新されたときに呼ばれるリスナー関数 19 | }, 20 | onDeleteTodo: () => { 21 | // 削除ボタンがクリックされたときによばれるリスナー関数 22 | } 23 | }); 24 | console.log(todoItemElement); //
  • 要素が入る 25 | //! [main] 26 | // Test 27 | console.log(todoItemElement.innerHTML.includes("あたらしいTodo")); // => true 28 | console.log(!todoItemElement.innerHTML.includes("checked")); // => true 29 | console.log(todoItemElement.innerHTML.includes("delete")); // => true 30 | cleanup(); 31 | -------------------------------------------------------------------------------- /tools/update-package-lock.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Usage: node tools/update-package-lock.mjs 3 | * Description: This script updates the package-lock.json for aligned with npm version 4 | */ 5 | import { globby } from "globby"; 6 | import { dirname, join } from "node:path"; 7 | import { fileURLToPath } from "node:url"; 8 | import { execFile } from "node:child_process"; 9 | // iterate directories which has package-lock.json 10 | // and exec `npm install` to update the package-lock.json 11 | const __dirname = dirname(fileURLToPath(import.meta.url)); 12 | const projectRoot = join(__dirname, ".."); 13 | const files = await globby([ 14 | join("source/use-case", "**", "package-lock.json"), 15 | // exclude node_modules 16 | "!node_modules/**", 17 | ], { 18 | gitignore: true, 19 | cwd: projectRoot, 20 | }); 21 | for (const file of files) { 22 | const dir = dirname(file); 23 | console.info(`Updating package-lock.json in ${dir}`); 24 | execFile("npm", ["install"], { 25 | cwd: join(projectRoot, dir) 26 | }); 27 | console.info(`Updated package-lock.json in ${dir}`); 28 | } 29 | -------------------------------------------------------------------------------- /source/use-case/todoapp/app-structure/todo-html/screenshot.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | declare projectDir=$(git rev-parse --show-toplevel); 4 | declare todoappDir="${projectDir}/source/use-case/todoapp" 5 | declare currentSectionDir="${todoappDir}/app-structure" 6 | declare currentDir="${todoappDir}/app-structure/todo-html" 7 | declare screenshot="${projectDir}/tools/applescript/lib/src/screenshot.js"; 8 | declare screenshotDevTools="${projectDir}/tools/applescript/lib/src/screenshot-dev-tools.js"; 9 | declare launchFirefox="${projectDir}/tools/applescript/lib/src/launch-firefox.js"; 10 | declare screenshotOnly="${projectDir}/tools/applescript/lib/src/screenshot-only.js"; 11 | 12 | # スクショ 13 | cd "${currentDir}" 14 | mkdir -p "${currentSectionDir}/img/" 15 | npx -q @js-primer/local-server . & 16 | npx -q wait-on http://localhost:3000 \ 17 | && node "${screenshot}" --url "http://localhost:3000/" --output "${currentSectionDir}/img/todo-html.png" 18 | 19 | # server 終了 20 | function finish { 21 | echo "Shutting down the server..." 22 | pkill js-primer-local-server 23 | } 24 | trap finish INT KILL TERM EXIT 25 | -------------------------------------------------------------------------------- /source/use-case/todoapp/entrypoint/first-entry/screenshot.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | declare projectDir=$(git rev-parse --show-toplevel); 4 | declare todoappDir="${projectDir}/source/use-case/todoapp" 5 | declare currentSectionDir="${todoappDir}/entrypoint" 6 | declare currentDir="${todoappDir}/entrypoint/first-entry" 7 | declare screenshotDevTools="${projectDir}/tools/applescript/lib/src/screenshot-dev-tools.js"; 8 | declare screenshot="${projectDir}/tools/applescript/lib/src/screenshot.js"; 9 | declare launchFirefox="${projectDir}/tools/applescript/lib/src/launch-firefox.js"; 10 | declare screenshotOnly="${projectDir}/tools/applescript/lib/src/screenshot-only.js"; 11 | 12 | cd "${currentDir}" 13 | # スクリーンショット 14 | mkdir -p "${currentSectionDir}/img/" 15 | npx --yes -q @js-primer/local-server . & 16 | npx --yes -q wait-on http://localhost:3000 \ 17 | && node "${screenshotDevTools}" --url "http://localhost:3000/" --output "${currentSectionDir}/img/first-entry.png" 18 | 19 | # server 終了 20 | function finish { 21 | echo "Shutting down the server..." 22 | pkill js-primer-local-server 23 | } 24 | trap finish INT TERM EXIT 25 | -------------------------------------------------------------------------------- /source/use-case/todoapp/final/more/src/view/TodoListView.js: -------------------------------------------------------------------------------- 1 | import { element } from "./html-util.js"; 2 | import { TodoItemView } from "./TodoItemView.js"; 3 | 4 | export class TodoListView { 5 | /** 6 | * `todoItems`に対応するTodoリストのHTML要素を作成して返す 7 | * @param {TodoItemModel[]} todoItems TodoItemModelの配列 8 | * @param {function({id:number, completed: boolean})} onUpdateTodo チェックボックスの更新イベントリスナー 9 | * @param {function({id:number})} onDeleteTodo 削除ボタンのクリックイベントリスナー 10 | * @returns {Element} TodoItemModelの配列に対応したリストのHTML要素 11 | */ 12 | createElement(todoItems, { onUpdateTodo, onDeleteTodo }) { 13 | const todoListElement = element`
      `; 14 | // 各TodoItemモデルに対応したHTML要素を作成し、リスト要素へ追加する 15 | todoItems.forEach(todoItem => { 16 | const todoItemView = new TodoItemView(); 17 | const todoItemElement = todoItemView.createElement(todoItem, { 18 | onDeleteTodo, 19 | onUpdateTodo 20 | }); 21 | todoListElement.appendChild(todoItemElement); 22 | }); 23 | return todoListElement; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /source/use-case/todoapp/app-structure/todo-html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Todo App 6 | 7 | 11 | 12 | 13 | 14 |
      15 | 16 |
      17 | 24 |
      25 | 26 |
      27 | 28 |
      29 |
      30 | 31 | Todoアイテム数: 0 32 |
      33 |
      34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /source/use-case/todoapp/final/final/src/view/TodoListView.js: -------------------------------------------------------------------------------- 1 | import { element } from "./html-util.js"; 2 | import { TodoItemView } from "./TodoItemView.js"; 3 | 4 | export class TodoListView { 5 | /** 6 | * `todoItems`に対応するTodoリストのHTML要素を作成して返す 7 | * @param {TodoItemModel[]} todoItems TodoItemModelの配列 8 | * @param {function({id:number, completed: boolean})} onUpdateTodo チェックボックスの更新イベントリスナー 9 | * @param {function({id:number})} onDeleteTodo 削除ボタンのクリックイベントリスナー 10 | * @returns {Element} TodoItemModelの配列に対応したリストのHTML要素 11 | */ 12 | createElement(todoItems, { onUpdateTodo, onDeleteTodo }) { 13 | const todoListElement = element`
        `; 14 | // 各TodoItemモデルに対応したHTML要素を作成し、リスト要素へ追加する 15 | todoItems.forEach(todoItem => { 16 | const todoItemView = new TodoItemView(); 17 | const todoItemElement = todoItemView.createElement(todoItem, { 18 | onDeleteTodo, 19 | onUpdateTodo 20 | }); 21 | todoListElement.appendChild(todoItemElement); 22 | }); 23 | return todoListElement; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /source/basic/string-unicode/Unicode-Column.md: -------------------------------------------------------------------------------- 1 | --- 2 | author: azu 3 | description: "[コラム] Unicodeにまつわる問題" 4 | sponsors: [] 5 | --- 6 | 7 | 8 | 9 | ### [コラム] Unicodeにまつわる問題 {#unicode-issues} 10 | 11 | この章では、最初に述べたようにUnicodeのUTF-16に関する一部分だけを紹介しています。 12 | この章で紹介していたのは、Code UnitとCode Pointに関する問題の一部分のみです。 13 | 14 | Unicodeにまつわる問題は、この他にもさまざまなものがありますが、それらはJavaScript以外の言語でも発生します。 15 | それらについてすべてを詳細に紹介することは難しいため、関連するキーワードを一部紹介します。 16 | 17 | - 書記素クラスタにまつわる問題: 書記素クラスタはUnicodeにおける自然な1文字を表す単位 18 | - 書記素クラスタにおける「1文字」は複数のCode Pointで表現されることがあります 19 | - JavaScriptには書記素クラスタの判定(「1文字」の判定)をする標準APIはありません 20 | - 関連する仕様の提案としてECMA i18n APIに[Intl.Segmenter](https://github.com/tc39/proposal-intl-segmenter)があります 21 | - Emoji ZWJ(Zero Width Joiner)Sequences: 絵文字の肌の色などを複数のCode Pointで表現するための仕様 22 | - 表示(グリフ)としては1文字として扱いたいケースが多くなるため、書記素クラスタの判定の問題につながる 23 | - カーソルの移動などは、書記素クラスタ単位での移動として扱いたい場合があります 24 | - 結合文字列と正規化の問題: macOSで濁点が分離した文字として扱われる問題などが有名 25 | -`String`の`normalize`メソッドが正規化に関連します 26 | - East Asian Width(東アジアの文字幅)の問題: ターミナルで日本語が表示される場合に位置がずれる問題などが有名 27 | - それぞれの文字(Code Point)の文字幅の違いから表示のずれが発生しています 28 | -------------------------------------------------------------------------------- /source/use-case/todoapp/final/create-view/src/view/TodoListView.js: -------------------------------------------------------------------------------- 1 | import { element } from "./html-util.js"; 2 | import { TodoItemView } from "./TodoItemView.js"; 3 | 4 | export class TodoListView { 5 | /** 6 | * `todoItems`に対応するTodoリストのHTML要素を作成して返す 7 | * @param {TodoItemModel[]} todoItems TodoItemModelの配列 8 | * @param {function({id:number, completed: boolean})} onUpdateTodo チェックボックスの更新イベントリスナー 9 | * @param {function({id:number})} onDeleteTodo 削除ボタンのクリックイベントリスナー 10 | * @returns {Element} TodoItemModelの配列に対応したリストのHTML要素 11 | */ 12 | createElement(todoItems, { onUpdateTodo, onDeleteTodo }) { 13 | const todoListElement = element`
          `; 14 | // 各TodoItemモデルに対応したHTML要素を作成し、リスト要素へ追加する 15 | todoItems.forEach(todoItem => { 16 | const todoItemView = new TodoItemView(); 17 | const todoItemElement = todoItemView.createElement(todoItem, { 18 | onDeleteTodo, 19 | onUpdateTodo 20 | }); 21 | todoListElement.appendChild(todoItemElement); 22 | }); 23 | return todoListElement; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016-present jsprimer project 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /source/basic/string/specs/add-regexp-escape-docs/requirements.md: -------------------------------------------------------------------------------- 1 | # 要件ファイル: RegExp.escape メソッドのドキュメント追加 2 | 3 | ## 概要 4 | ES2025で追加される`RegExp.escape`メソッドについて、js-primerの文字列の章にドキュメントを追加する。 5 | 6 | ## 機能要件 7 | 8 | ### 1. ドキュメントの追加位置 9 | - `/source/basic/string/README.md`ファイルに追加 10 | - 正規表現関連のセクション(文字列の検索や置換の文脈)に追加 11 | 12 | ### 2. 説明内容 13 | - `RegExp.escape`メソッドの基本的な説明 14 | - メソッドの目的:正規表現の特殊文字を安全にエスケープする 15 | - ES2025で追加される新機能であることを明記 16 | 17 | ### 3. コード例 18 | - 基本的な使用例を提供 19 | - `replaceAll`メソッドと組み合わせた実用的な例 20 | - エスケープが必要な特殊文字の例(例:`?`、`.`、`*`など) 21 | 22 | ### 4. 説明のトーン 23 | - 初心者にも理解しやすい簡潔な説明 24 | - 必須ではないが便利な機能であることを伝える 25 | - 複雑になりすぎない程度の説明に留める 26 | - 既存の文章を参考にする source/basic/string/README.md を合わせる 27 | 28 | ## 非機能要件 29 | 30 | ### 1. 文書の一貫性 31 | - 既存のjs-primerの文体・形式に合わせる 32 | - 他のメソッドの説明と同じレベルの詳細度 33 | 34 | ### 2. コード例の品質 35 | - 実行可能で正確なコード例 36 | - コメントを適切に付けて理解しやすくする 37 | 38 | ### 3. 互換性の言及 39 | - ES2025の機能であることを明確にする 40 | - 必要に応じてブラウザサポート状況への言及 41 | 42 | ## 制約事項 43 | - 軽い説明に留める(issueの要望通り) 44 | - 過度に技術的な詳細は避ける 45 | - 初心者向けのプライマーの性質を維持する 46 | 47 | ## 成功基準 48 | - `RegExp.escape`の基本的な使い方が理解できる 49 | - なぜこのメソッドが便利なのかが伝わる 50 | - 既存のドキュメントと自然に統合されている 51 | -------------------------------------------------------------------------------- /source/basic/loop/test/while/while-add-example-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import strictEval from "strict-eval"; 3 | import fs from "fs"; 4 | import url from "node:url"; 5 | import path from "node:path"; 6 | import { describe, it } from "node:test"; 7 | 8 | const __filename__ = url.fileURLToPath(import.meta.url); 9 | const __dirname = path.dirname(__filename__); 10 | const Code = fs.readFileSync(path.join(__dirname, "../../src/while/while-add-example.js"), "utf-8"); 11 | describe("whileの加算", function() { 12 | it("0以上から10未満の値を出力する", function() { 13 | const actualLogs = []; 14 | const console = { 15 | log(message) { 16 | actualLogs.push(message); 17 | } 18 | }; 19 | strictEval(Code, { 20 | console 21 | }); 22 | assert.deepEqual(actualLogs, [ 23 | `ループ開始前のxの値: ${0}`, 24 | 0, 25 | 1, 26 | 2, 27 | 3, 28 | 4, 29 | 5, 30 | 6, 31 | 7, 32 | 8, 33 | 9, 34 | `ループ終了後のxの値: ${10}` 35 | ]); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /source/basic/object/OUTLINE.md: -------------------------------------------------------------------------------- 1 | # 目的 2 | 3 | すべてのもととなる"オブジェクト"について説明する 4 | 5 | - オブジェクトとはなにか 6 | - オブジェクトがどのように作られるか 7 | - オブジェクトのインスタンス 8 | - オブジェクトのインスタンスメソッド 9 | - オブジェクトの静的メソッド 10 | 11 | ## 目的外 12 | 13 | - prototypeオブジェクト -> 次の章 14 | - prototypeメソッド 15 | - prototypeチェーン 16 | 17 | ## アウトライン 18 | 19 | - オブジェクト 20 | - オブジェクトとは 21 | - オブジェクトの作成方法 = インスタンスの作成 22 | - `Object`もオブジェクトで紛らわしい 23 | - `Object`から作られたオブジェクトを**オブジェクトのインスタンス**と呼ぶことにする 24 | - オブジェクトリテラルは`Object`を継承した**オブジェクトのインスタンス**を作る構文 25 | - プロパティへのアクセス方法 26 | - プロパティの参照 27 | - undefinedを返す 28 | - プロパティの更新 29 | - プロパティの追加 30 | - プロパティの削除 31 | - [コラム] constで定義したオブジェクトは変更可能 32 | - 存在しないプロパティのネストは例外を返す 33 | - プロパティが定義済みかを確認する方法 34 | - undefinedとの比較 35 | - in演算子を使う 36 | - `hasOwnProperty`メソッド(インスタンスメソッド) 37 | - Optional chaining(`?.`)でのアクセス方法 38 | - オブジェクトの静的メソッド 39 | - オブジェクトのプロパティの列挙 40 | - `Object.keys`メソッド 41 | - `Object.values`メソッド 42 | - `Object.entries` 43 | - オブジェクトのコピー 44 | - `Object.assign` 45 | - オブジェクトのマージ 46 | - `Object.assign` 47 | - spread構文 48 | - まとめ 49 | - `Object`の提供するメソッド 50 | - インスタンスメソッドはどのように定義されているか -> 次の章へ 51 | -------------------------------------------------------------------------------- /source/use-case/nodecli/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | author: laco 3 | description: "Node.jsでCLI(コマンドラインインターフェース)アプリケーションを開発する例として、MarkdownをHTMLに変換するツールを作成していきます。また、Node.jsやnpmの使い方を紹介します。" 4 | sponsors: [] 5 | --- 6 | 7 | # ユースケース: Node.jsでCLIアプリケーション {#node-cli} 8 | 9 | ここではNode.jsでCLI(コマンドラインインターフェース)アプリケーションを開発します。 10 | CLIのユースケースとしてMarkdown形式のテキストファイルをHTMLテキストに変換するツールを作成します。 11 | 12 | 作成するアプリケーションは次の要件を満たすものとします。 13 | 14 | - コマンドライン引数として変換対象のファイルパスを受け取る 15 | - Markdown形式のファイルを読み込み、変換したHTMLを標準出力に表示する 16 | - 変換の設定をコマンドライン引数でオプションとして与えられる 17 | 18 | ## 目次 {#summary} 19 | 20 | ### [Node.jsでHello World](./helloworld/README.md) {#helloworld} 21 | 22 | Hello Worldアプリケーションを通じてNode.jsのCLIアプリケーションの基本を学びます。 23 | 24 | ### [コマンドライン引数を処理する](./argument-parse/README.md) {#argument-parse} 25 | 26 | コマンドライン引数を受け取り、アプリケーションから使いやすい形にパースする方法を学びます。 27 | 28 | ### [ファイルを読み込む](./read-file/README.md) {#read-file} 29 | 30 | Node.jsの`fs`モジュールを使ったファイルの読み込みについて学びます。 31 | 32 | ### [MarkdownをHTMLに変換する](./md-to-html/README.md) {#md-to-html} 33 | 34 | markedパッケージを使ってMarkdownファイルをHTMLに変換します。 35 | 36 | ### [ユニットテストを記述する](./refactor-and-unittest/README.md) {#refactor-and-unittest} 37 | 38 | ユニットテストの導入とソースコードのモジュール化を行います。 39 | -------------------------------------------------------------------------------- /source/basic/string-unicode/img/codeunit-codepoint-table.xml: -------------------------------------------------------------------------------- 1 | 3VbbjtowEP0aPyI5NoTkEVjoTStVolWfTexcugZTxyzZfn3HsXMDFgVpnwoSTM4cj2dOkhkjutpXnzQ75s+KC4kI5hWiT4iQeIbh1wJvDgjj0AGZLriDgg7YFn+FB/267FRwUQ6IRilpiuMQTNThIBIzwJjW6jykpUoOdz2yTFwB24TJa/RXwU3u0IjMO/yzKLK82TkIY+fZs4bsKylzxtW5B9E1oiutlHHWvloJabVrdHHrNu9428S0OJgxCzbz+aSclr+fo+3X6gv+ps/ibeKjvDJ58gUjEkqItzRsBxoQvFOaC137wj8nm+wy6EwgnFtZWj+GTGYDUgK5HBnnxSEbUKcDVmnevO4N6IMvwNlExY6be9VvuFzKk0RJyY6lcJz2qrdhmPl/V/FOwVN6Ceqbee1Y8pJpdTpwu43Sbg9EqGAp4fQiFZ/lsbKxlIQ7UnPjaJeS4E5CeYusKYoIWkxrg6Il9UY0bwzacDbeWMZNHHgmeqGuguORvGAkj9zlWVDfUvkC4V3tIYKmYSudocUTimsjiqzRi8nvRXDKkAf5iwf507v8hwv/eSgSaKd19AAt1it7QfB3VcALv96g5QpUCEK0jlGEEchuhZohuJ/eGY/MH1cUjxanJo9VpiZ/tCw/NhNb9VAVEOt/F8WCt1qUxV2zbnAy6FnEiMriudlLAAIwS6PVi1i5zgUUmmD7BU9aSNnD0/oDuHoVOpX1/LIUQOruJ+xwwe2mQILN3h1QQTv24Lgg1F4YDdXgZsHcT0p/VAias0M3YcgMeyzvD93Qg8wP+6yN3c1DMPxIbC670Vv7eucXuv4H -------------------------------------------------------------------------------- /source/basic/async/example/callback-chain.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 指定時間内にタイマーが発火されるなら成功、そうでないなら失敗 3 | * @param callback 4 | */ 5 | const asyncDo = (callback) => { 6 | // タイマーのコールバックを呼び出すまでの時間(ミリ秒) 7 | const delay = 10; 8 | // タイマーのコールバックが呼ばれるまで待てる時間(ミリ秒) 9 | const limitOfDelay = delay * 2; 10 | const startTime = Date.now(); 11 | setTimeout(() => { 12 | const diffTime = Date.now() - startTime; 13 | if (diffTime <= limitOfDelay) { 14 | callback(null, `許容時間内にタイマーが発火しました(${diffTime}ミリ秒)`); 15 | } else { 16 | callback(new Error(`許容時間内タイマーが発火できませんでした(${diffTime}ミリ秒)`)); 17 | } 18 | }, delay); 19 | }; 20 | 21 | const doTask = () => { 22 | const newArray = new Array(10e6).fill(0); 23 | console.log(`${newArray.length}コの配列を0で初期化しました`); 24 | }; 25 | 26 | asyncDo((error, message) => { 27 | if (error) { 28 | console.error(error); 29 | return; 30 | } 31 | console.log(message); 32 | asyncDo((error, message) => { 33 | if (error) { 34 | console.error(error); 35 | return; 36 | } 37 | console.log(message); 38 | }); 39 | 40 | doTask(); 41 | }); 42 | 43 | 44 | -------------------------------------------------------------------------------- /source/use-case/todoapp/form-event/add-todo-item/screenshot.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | declare projectDir=$(git rev-parse --show-toplevel); 4 | declare todoappDir="${projectDir}/source/use-case/todoapp" 5 | declare currentSectionDir="${todoappDir}/form-event" 6 | declare currentDir="${todoappDir}/form-event/add-todo-item" 7 | declare screenshot="${projectDir}/tools/applescript/lib/src/screenshot.js"; 8 | declare screenshotDevTools="${projectDir}/tools/applescript/lib/src/screenshot-dev-tools.js"; 9 | declare launchFirefox="${projectDir}/tools/applescript/lib/src/launch-firefox.js"; 10 | declare screenshotOnly="${projectDir}/tools/applescript/lib/src/screenshot-only.js"; 11 | 12 | # スクショ 13 | cd "${currentDir}" 14 | mkdir -p "${currentSectionDir}/img/" 15 | npx --yes -q @js-primer/local-server . & 16 | npx --yes -q wait-on http://localhost:3000 \ 17 | && node "${launchFirefox}" --url "http://localhost:3000/" \ 18 | && read -p "追加イベントのスクショ: 'テスト'を追加 -> Enter" \ 19 | && node "${screenshotOnly}" --output "${currentSectionDir}/img/add-todo-item.png" 20 | 21 | # server 終了 22 | function finish { 23 | echo "Shutting down the server..." 24 | pkill js-primer-local-server 25 | } 26 | trap finish INT TERM EXIT 27 | -------------------------------------------------------------------------------- /tools/applescript/modules/keyboard-util.ts: -------------------------------------------------------------------------------- 1 | import { run } from "@jxa/run"; 2 | 3 | export type ModifierOption = { 4 | shift?: boolean; 5 | control?: boolean; 6 | option?: boolean; 7 | command?: boolean; 8 | }; 9 | 10 | 11 | function createModifier(modifierOption: ModifierOption) { 12 | const modifiers = []; 13 | if (modifierOption.shift) { 14 | modifiers.push("shift down"); 15 | } 16 | if (modifierOption.command) { 17 | modifiers.push("command down"); 18 | } 19 | if (modifierOption.control) { 20 | modifiers.push("control down"); 21 | } 22 | if (modifierOption.option) { 23 | modifiers.push("option down"); 24 | 25 | } 26 | return modifiers; 27 | } 28 | 29 | export function sendKeyStroke(key: string, modifierOption: ModifierOption) { 30 | const modifiers = createModifier(modifierOption); 31 | return run((key, modifiers) => { 32 | const SystemEvents = Application("System Events"); 33 | const Firefox = Application("Firefox"); 34 | Firefox.activate(); 35 | delay(1.0); // wait for active 36 | SystemEvents.keystroke(key, { using: modifiers }); 37 | }, key, modifiers); 38 | } 39 | -------------------------------------------------------------------------------- /source/use-case/ajaxapp/http/screenshot-manual.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | declare projectDir=$(git rev-parse --show-toplevel); 4 | declare currentDir=$(pwd) 5 | declare screenshotDevTools="${projectDir}/tools/applescript/lib/src/screenshot-dev-tools.js"; 6 | declare screenshot="${projectDir}/tools/applescript/lib/src/screenshot.js"; 7 | declare launchFirefox="${projectDir}/tools/applescript/lib/src/launch-firefox.js"; 8 | declare screenshotOnly="${projectDir}/tools/applescript/lib/src/screenshot-only.js"; 9 | mkdir -p "${currentDir}/img/" 10 | echo "local server 起動" 11 | npx --yes -q @js-primer/local-server src/ & 12 | serverPID=$! 13 | echo "screenshotを撮影" 14 | npx --yes -q wait-on http://localhost:3000 \ 15 | && node "${launchFirefox}" --url "http://localhost:3000/" \ 16 | && read -p "コンソールログのスクショ: 開発者コンソールを開いて、ボタンをクリック -> Enter" \ 17 | && node "${screenshotOnly}" --output "${currentDir}/img/fig-1.png" --continue \ 18 | && read -p "ネットワークパネルのスクショ: ネットワークパネルを開く -> Enter" \ 19 | && node "${screenshotOnly}" --output "${currentDir}/img/fig-2.png" \ 20 | 21 | # server 終了 22 | function finish { 23 | echo "Shutting down the server..." 24 | pkill js-primer-local-server 25 | } 26 | trap finish INT TERM EXIT 27 | -------------------------------------------------------------------------------- /source/use-case/todoapp/form-event/prevent-event/screenshot.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | declare projectDir=$(git rev-parse --show-toplevel); 4 | declare todoappDir="${projectDir}/source/use-case/todoapp" 5 | declare currentSectionDir="${todoappDir}/form-event" 6 | declare currentDir="${todoappDir}/form-event/prevent-event" 7 | declare screenshot="${projectDir}/tools/applescript/lib/src/screenshot.js"; 8 | declare screenshotDevTools="${projectDir}/tools/applescript/lib/src/screenshot-dev-tools.js"; 9 | declare launchFirefox="${projectDir}/tools/applescript/lib/src/launch-firefox.js"; 10 | declare screenshotOnly="${projectDir}/tools/applescript/lib/src/screenshot-only.js"; 11 | 12 | # スクショ 13 | mkdir -p "${currentSectionDir}/img/" 14 | cd "${currentDir}" 15 | npx --yes -q @js-primer/local-server . & 16 | npx --yes -q wait-on http://localhost:3000 \ 17 | && node "${launchFirefox}" --devTools --url "http://localhost:3000/" \ 18 | && read -p "追加イベントのスクショ: 'テスト'を追加 > コンソールを開く -> Enter" \ 19 | && node "${screenshotOnly}" --output "${currentSectionDir}/img/prevent-event.png" 20 | 21 | # server 終了 22 | function finish { 23 | echo "Shutting down the server..." 24 | pkill js-primer-local-server 25 | } 26 | trap finish INT TERM EXIT 27 | -------------------------------------------------------------------------------- /source/intro/goal/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | author: azu 3 | description: "この書籍の目的、想定している読者対象について扱います。またこの書籍がやらないことを明確にしていきます。" 4 | sponsors: [] 5 | --- 6 | 7 | # 本書の目的 {#purpose} 8 | 9 | 本書の目的と読者対象を簡単にまとめたものです。 10 | 11 | この文章は、まだ不完全な状態であるため、次のIssueを参照してください。 12 | 13 | See [はじめに/本書の目的 · Issue #103 · asciidwango/js-primer](https://github.com/asciidwango/js-primer/issues/103 "はじめに/本書の目的 · Issue #103 · asciidwango/js-primer") 14 | 15 | - 本書がやらないこと 16 | - すべての文法や機能を網羅するのが目的ではない 17 | - ECMAScriptの仕様を厳密に学ぶことは目的ではない 18 | - DOMについて学ぶのが目的ではない 19 | - 他の言語と比較するのが目的ではない 20 | - Node.jsの使い方をマスターするのが目的ではない 21 | - JavaScriptのリファレンスを目指すものではない 22 | - 詳しくはMDNを参照すればよい 23 | - JavaScriptのライブラリを書くのが目的ではない 24 | - JavaScriptのライブラリの使い方を学ぶのが目的ではない 25 | - これを読んだから何か作れるというゴールがある訳ではない 26 | - 本書の目的 27 | - 文法とともに実際にどのようなケースで使われてるのかを知ること 28 | - 必要なものを必要なだけ学びJavaScriptを読み書きできるようになることが目的 29 | - JavaScriptは変化を取り入れている言語であるため、JavaScriptの変化に対して対応できる基礎をつけていく 30 | - 過去にGood Partsと呼ばれていたものが良くないものとなっていることがある 31 | - 何か問題があるときに、その解決方法を自分で調べることができるようにすること 32 | - 本書の読者対象 33 | - 完全なプログラミング初心者は対象ではない 34 | - JavaScript以外の言語をやったことがある人 35 | - JavaScriptを触ったことがある人 36 | - 古いJavaScriptを知っているが、今のJavaScriptはよくわからない人 37 | -------------------------------------------------------------------------------- /tools/applescript/src/launch-firefox.ts: -------------------------------------------------------------------------------- 1 | import { launchFirefox, quitFirefox, setFirefoxWindowBounds } from "../modules/firefox.js"; 2 | import { wait } from "../modules/wait.js"; 3 | import meow from "meow"; 4 | import { sendKeyStroke } from "../modules/keyboard-util.js"; 5 | 6 | const profileName = "js-primer"; 7 | 8 | 9 | const cli = meow(` 10 | Usage 11 | $ launch-firefox 12 | 13 | Options 14 | --url open url 15 | --devTools open devTools 16 | 17 | Examples 18 | $ launch-firefox --url "http://127.0.0.1:8080/final/final/" 19 | `, { 20 | flags: { 21 | url: { 22 | type: "string", 23 | isRequired: true 24 | }, 25 | devTools: { 26 | type: "boolean" 27 | } 28 | } 29 | }); 30 | (async function() { 31 | await quitFirefox(); 32 | await wait(1000); 33 | await launchFirefox({ 34 | url: cli.flags.url, 35 | profileName 36 | }); 37 | await wait(1000); 38 | await setFirefoxWindowBounds(); 39 | await wait(1000); 40 | if (cli.flags.devTools) { 41 | await sendKeyStroke("i", { 42 | command: true, 43 | option: true 44 | }); 45 | } 46 | })(); 47 | -------------------------------------------------------------------------------- /textlint/textlint-rule-static-method-syntax/index.js: -------------------------------------------------------------------------------- 1 | import StaticMethods from "./static-methods.json" with { type: "json" }; 2 | 3 | /** 4 | * 静的メソッドの表記を 5 | * 6 | * `Promise.resolve`静的メソッド 7 | * 8 | * のように `XXX`静的メソッド と表記するように統一するtextlintルール 9 | * @param context 10 | * @param options 11 | * @returns {import("@textlint/types").TextlintRuleModule} 12 | */ 13 | const report = (context, options = {}) => { 14 | const { Syntax, RuleError, report, getSource, fixer } = context; 15 | return { 16 | [Syntax.Code](node) { 17 | const text = node.value; 18 | const isStaticMethod = StaticMethods.includes(text); 19 | if (!isStaticMethod) { 20 | return; 21 | } 22 | const code = getSource(node, 0, 4); 23 | if (/メソッド$/.test(code)) { 24 | const originalCode = getSource(node); 25 | // 静的メソッドとするべき 26 | report(node, new RuleError(`"${code}"は、"${originalCode}静的メソッド"にしてください`, { 27 | index: 0, 28 | fix: fixer.insertTextAfter(node, "静的") 29 | })); 30 | } 31 | } 32 | }; 33 | }; 34 | export default { 35 | linter: report, fixer: report 36 | }; 37 | -------------------------------------------------------------------------------- /source/use-case/todoapp/cypress/integration/form-event/add-todo-item/add-todo-item-spec.js: -------------------------------------------------------------------------------- 1 | const addNewTodo = require("../../../helper/todo-helper").addNewTodo; 2 | const URL = "/form-event/add-todo-item"; 3 | describe(URL, function() { 4 | it("入力欄を埋めて送信するとTodoアイテム(li)のみが追加される", function() { 5 | cy.visit(URL); 6 | const inputText = "test"; 7 | addNewTodo(inputText).then(() => { 8 | // ulがある 9 | cy.get("#js-todo-list ul").should(ul => { 10 | expect(ul).to.have.length(1); 11 | }); 12 | // liはある 13 | cy.get("#js-todo-list li").should(items => { 14 | expect(items).to.have.length(1); 15 | }); 16 | // countは増える 17 | cy.get("#js-todo-count").should(count => { 18 | expect(count).to.contain("1"); 19 | }); 20 | }); 21 | addNewTodo(inputText).then(() => { 22 | // liが増える 23 | cy.get("#js-todo-list li").should(items => { 24 | expect(items).to.have.length(2); 25 | }); 26 | cy.get("#js-todo-count").should(count => { 27 | expect(count).to.contain("2"); 28 | }); 29 | }); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /source/use-case/todoapp/cypress/integration/update-delete/add-checkbox/add-checkbox.js: -------------------------------------------------------------------------------- 1 | const URL = "/update-delete/add-checkbox"; 2 | const addNewTodo = require("../../../helper/todo-helper").addNewTodo; 3 | describe(URL, function() { 4 | it("input[type=checkbox]が追加される", function() { 5 | cy.visit(URL); 6 | // checkbox は 0コ 7 | cy.get(".checkbox").should(items => { 8 | expect(items).to.have.length(0); 9 | }); 10 | const inputText = "テスト"; 11 | addNewTodo(inputText).then(() => { 12 | // checkbox は 1コ 13 | cy.get(".checkbox").should(items => { 14 | expect(items).to.have.length(1); 15 | }); 16 | // checkedは 1コ 17 | cy.get(".checkbox").check(); 18 | cy.get(".checkbox").should("be.checked"); 19 | }); 20 | addNewTodo(inputText).then(() => { 21 | // 新しく追加するとcheckedが消える 22 | cy.get(".checkbox[checked]").should(items => { 23 | expect(items).to.have.length(0); 24 | }); 25 | // checkbox は 2コ 26 | cy.get(".checkbox").should(items => { 27 | expect(items).to.have.length(2); 28 | }); 29 | }); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /source/use-case/todoapp/cypress/integration/event-model/event-emitter/event-emitter-spec.js: -------------------------------------------------------------------------------- 1 | const addNewTodo = require("../../../helper/todo-helper").addNewTodo; 2 | const URL = "/event-model/event-emitter"; 3 | describe(URL, function() { 4 | it("入力欄を埋めて送信するとTodoリストにTodoアイテムが追加される", function() { 5 | cy.visit(URL); 6 | const inputText = "test"; 7 | addNewTodo(inputText).then(() => { 8 | // ulはある 9 | cy.get("#js-todo-list ul").should(items => { 10 | expect(items).to.have.length(1); 11 | }); 12 | // liはある 13 | cy.get("#js-todo-list li").should(items => { 14 | expect(items).to.have.length(1); 15 | }); 16 | // countは増える 17 | cy.get("#js-todo-count").should(count => { 18 | expect(count).to.contain("1"); 19 | }); 20 | }); 21 | addNewTodo(inputText).then(() => { 22 | // liが増える 23 | cy.get("#js-todo-list li").should(items => { 24 | expect(items).to.have.length(2); 25 | }); 26 | cy.get("#js-todo-count").should(count => { 27 | expect(count).to.contain("2"); 28 | }); 29 | }); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /source/use-case/todoapp/event-model/event-emitter/src/model/TodoListModel.js: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from "../EventEmitter.js"; 2 | 3 | export class TodoListModel extends EventEmitter { 4 | #items; 5 | /** 6 | * @param {TodoItemModel[]} [items] 初期アイテム一覧(デフォルトは空の配列) 7 | */ 8 | constructor(items = []) { 9 | super(); 10 | this.#items = items; 11 | } 12 | 13 | /** 14 | * TodoItemの合計個数を返す 15 | * @returns {number} 16 | */ 17 | getTotalCount() { 18 | return this.#items.length; 19 | } 20 | 21 | /** 22 | * 表示できるTodoItemの配列を返す 23 | * @returns {TodoItemModel[]} 24 | */ 25 | getTodoItems() { 26 | return this.#items; 27 | } 28 | 29 | /** 30 | * TodoListの状態が更新されたときに呼び出されるリスナー関数を登録する 31 | * @param {Function} listener 32 | */ 33 | onChange(listener) { 34 | this.addEventListener("change", listener); 35 | } 36 | 37 | /** 38 | * 状態が変更されたときに呼ぶ。登録済みのリスナー関数を呼び出す 39 | */ 40 | emitChange() { 41 | this.emit("change"); 42 | } 43 | 44 | /** 45 | * TodoItemを追加する 46 | * @param {TodoItemModel} todoItem 47 | */ 48 | addTodo(todoItem) { 49 | this.#items.push(todoItem); 50 | this.emitChange(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /source/use-case/todoapp/update-delete/add-checkbox/src/model/TodoListModel.js: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from "../EventEmitter.js"; 2 | 3 | export class TodoListModel extends EventEmitter { 4 | #items; 5 | /** 6 | * @param {TodoItemModel[]} [items] 初期アイテム一覧(デフォルトは空の配列) 7 | */ 8 | constructor(items = []) { 9 | super(); 10 | this.#items = items; 11 | } 12 | 13 | /** 14 | * TodoItemの合計個数を返す 15 | * @returns {number} 16 | */ 17 | getTotalCount() { 18 | return this.#items.length; 19 | } 20 | 21 | /** 22 | * 表示できるTodoItemの配列を返す 23 | * @returns {TodoItemModel[]} 24 | */ 25 | getTodoItems() { 26 | return this.#items; 27 | } 28 | 29 | /** 30 | * TodoListの状態が更新されたときに呼び出されるリスナー関数を登録する 31 | * @param {Function} listener 32 | */ 33 | onChange(listener) { 34 | this.addEventListener("change", listener); 35 | } 36 | 37 | /** 38 | * 状態が変更されたときに呼ぶ。登録済みのリスナー関数を呼び出す 39 | */ 40 | emitChange() { 41 | this.emit("change"); 42 | } 43 | 44 | /** 45 | * TodoItemを追加する 46 | * @param {TodoItemModel} todoItem 47 | */ 48 | addTodo(todoItem) { 49 | this.#items.push(todoItem); 50 | this.emitChange(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /source/basic/string-unicode/img/emoji-codeunit-codepoint-table.xml: -------------------------------------------------------------------------------- 1 | 3VZNb+MgEP01HCthcF37mKROdw9VV2pXe1xhgz+2xLjEad399TsY/NWkkVv1tImUDG+eh+FBwkN0s2tvNKuLW8WFRATzFtFrREhAKXwa4NUCfuBbINclt5A3AvflX+FA7NBDycV+RmyUkk1Zz8FUVZVImxnGtFYvc1qm5HzWmuXiCLhPmTxGf5W8KSwakqsR/ybKvOhn9oLIZnasJ7uV7AvG1csEojGiG61UY6NduxHSaNfrYp/bvpMdGtOiapY8QJ7v6ts/T3fxw93PG3WbfVfp7wsvtGWemTy4FSMSSCi4blgCIhCcKM2F7nLB08F0u/bGEAgvgy5DHkMrlzNSCs3UjPOyymdUf8baN69O+B50xVeQ7Ktiyy2c7CdStuWLVEnJ6r2wnGE0nZDJMq9mE6YgJyx3Qgpy921lSRSc5begPtl8wtLHXKtDxU0vSttGEKGCZYTTN/26pdStqaUk7FvHjcIkI96ZhooBiSkKCVr5XUDRmrogvOoD2nO2LlhHfR04OZNSR8XxQp63kEcW8uhCnn+WZ0B9atfeIHzUMkBR0Cl3iVbXKOqCMDTBpCY/V6HTe7XqU8nRdMsqjBJ8rgJssv+JCuYHs6/Z/NdBTpzDLUbRFoXXKIzPFv3wHvysyhTuk24RHlrFGzMg+Icq4R8v3qL1BjbEC1AcoRAj6M3s2SWCo+qS0UKZcEuxYB8gZ/QD5MT/Mq1x62XUF18s88P2wqg4VxnE/89E7sk8pOlycvYZuQ146p4wuL1We5zMLg7SiNbgRbOTAHgQ7hutHsXGXh9AoSk2b8hkpZQTPOtegKtnoTPZWQ1DAaS7goTxAXiYFEgw2btewhscCjg7oXai0bAa3D9Analxrs4L3Hj0AuD0HFZM/VEPMufL8qH2aF0gcO6lH44uqctNrCaN/wE= -------------------------------------------------------------------------------- /tools/applescript/src/screenshot.ts: -------------------------------------------------------------------------------- 1 | import { launchFirefox, quitFirefox, screenshotFirefox, setFirefoxWindowBounds } from "../modules/firefox.js"; 2 | import * as path from "path"; 3 | import { wait } from "../modules/wait.js"; 4 | import meow from "meow"; 5 | 6 | const profileName = "js-primer"; 7 | 8 | 9 | const cli = meow(` 10 | Usage 11 | $ screenshot 12 | 13 | Options 14 | --url open url 15 | --output, -o output path of image 16 | 17 | Examples 18 | $ screenshot --url "http://127.0.0.1:8080/final/final/" --output ./output.png 19 | `, { 20 | flags: { 21 | url: { 22 | type: "string", 23 | isRequired: true 24 | }, 25 | output: { 26 | type: "string", 27 | alias: "o", 28 | isRequired: true 29 | } 30 | } 31 | }); 32 | const outputFilePath = path.resolve(process.cwd(), cli.flags.output); 33 | (async function() { 34 | await quitFirefox(); 35 | await wait(1000); 36 | await launchFirefox({ 37 | url: cli.flags.url, 38 | profileName 39 | }); 40 | await wait(1000); 41 | await setFirefoxWindowBounds(); 42 | await wait(1000); 43 | await screenshotFirefox(outputFilePath); 44 | await quitFirefox(); 45 | })(); 46 | -------------------------------------------------------------------------------- /source/basic/read-eval-print/OUTLINE.md: -------------------------------------------------------------------------------- 1 | # コードの評価と結果の表示 2 | 3 | 目的: ブラウザでのコード評価方法、Console APIの使い方、構文エラーとデバッグについて学ぶ 4 | 5 | - ブラウザ 6 | - Firefox 7 | - 開発者 8 | - コンソール 9 | 10 | ここではブラウザの評価方法と確認方法について扱う。 11 | エラーが発生したときに何をするかを考える。 12 | 13 | ## 扱わないこと 14 | 15 | - Node.js 16 | - デバッグ方法 17 | - Node.jsのREPL 18 | - ブレークポイント 19 | 20 | ## アウトライン 21 | 22 | - ブラウザの選択 => Firefox 23 | - ブラウザでの実行方法 24 | - index.htmlとindex.jsを作成しscriptとして読み込んで実行する 25 | - Console APIの使い方を学ぶ 26 | - 開発者ツールの開き方 27 | - コードの実行方法、REPL 28 | - (ウェブ版) ウェブ版ではこの方法はワンクリックでできるようになっている 29 | - Node.jsについては2章であつかいます 30 | - 構文エラー(SyntaxError)が出る場合について 31 | - 例) `console.log(1;` 32 | - スタックトレースの簡単な読み方 33 | - 詳細はtry...catchの章 34 | - 構文エラーは人間向けのエラーではない問題 35 | - 例) `cosnt a = 1;` 36 | - エラーの行例の前後も見てみよう 37 | - Firefoxではコンソールにエラーの詳細へのリンクがでるぞ 38 | - 実行時エラー(ランタイムエラー) 39 | - 例): `console.logs()` 40 | - 構文としては正しいが実行時に問題があり例外発生するもの 41 | - `TypeError`など`SyntaxError`以外のもの 42 | - 実行時エラーは単純なものから複雜なもの(値に依存するもの)がある 43 | - デバッグ 44 | - あわてずにエラーの原因を追っていく => デバッグ 45 | - エラー文を検索する or MDNのエラーで該当してないものがあるものをみる or コードをよく読む 46 | - SyntaxError => あなたの書き方がなにか間違っています 47 | - TypeError => コードの動かしやメソッドの呼び出し方が何かおかしいです。どこでおかしくなったのかをconsole APIで見よう 48 | - まとめ 49 | - コードの実行 50 | - ログの出し方 51 | - エラーの種類 52 | - デバッグ -------------------------------------------------------------------------------- /source/use-case/nodecli/refactor-and-unittest/src/test/md2html-test.js: -------------------------------------------------------------------------------- 1 | import { test } from "node:test"; 2 | import * as assert from "node:assert"; 3 | import * as fs from "node:fs/promises"; 4 | import { md2html } from "../md2html.js"; 5 | 6 | test("converts Markdown to HTML (GFM=false)", async() => { 7 | // fs.readFileはPromiseを返すので、`await`式で読み込みが完了するまで待って内容を取得する 8 | const sample = await fs.readFile("test/fixtures/sample.md", { 9 | encoding: "utf8", 10 | }); 11 | const expected = await fs.readFile("test/fixtures/expected.html", { 12 | encoding: "utf8", 13 | }); 14 | // 末尾の改行の有無の違いを無視するため、変換後のHTMLのスペースをtrimメソッドで削除してから比較しています 15 | assert.strictEqual( 16 | md2html(sample, { gfm: false }).trimEnd(), 17 | expected.trimEnd(), 18 | ); 19 | }); 20 | 21 | test("converts Markdown to HTML (GFM=true)", async() => { 22 | const sample = await fs.readFile("test/fixtures/sample.md", { 23 | encoding: "utf8", 24 | }); 25 | const expected = await fs.readFile("test/fixtures/expected-gfm.html", { 26 | encoding: "utf8", 27 | }); 28 | // 末尾の改行の有無の違いを無視するため、変換後のHTMLのスペースをtrimメソッドで削除してから比較しています 29 | assert.strictEqual( 30 | md2html(sample, { gfm: true }).trimEnd(), 31 | expected.trimEnd(), 32 | ); 33 | }); 34 | -------------------------------------------------------------------------------- /source/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-primer", 3 | "short_name": "js-primer", 4 | "theme_color": "#000000", 5 | "background_color": "#ffffff", 6 | "display": "standalone", 7 | "start_url": "https://jsprimer.net/", 8 | "icons": [ 9 | { 10 | "src": "./gitbook/icons/icon-72x72.png", 11 | "sizes": "72x72", 12 | "type": "image/png" 13 | }, 14 | { 15 | "src": "./gitbook/icons/icon-96x96.png", 16 | "sizes": "96x96", 17 | "type": "image/png" 18 | }, 19 | { 20 | "src": "./gitbook/icons/icon-128x128.png", 21 | "sizes": "128x128", 22 | "type": "image/png" 23 | }, 24 | { 25 | "src": "./gitbook/icons/icon-144x144.png", 26 | "sizes": "144x144", 27 | "type": "image/png" 28 | }, 29 | { 30 | "src": "./gitbook/icons/icon-152x152.png", 31 | "sizes": "152x152", 32 | "type": "image/png" 33 | }, 34 | { 35 | "src": "./gitbook/icons/icon-192x192.png", 36 | "sizes": "192x192", 37 | "type": "image/png" 38 | }, 39 | { 40 | "src": "./gitbook/icons/icon-384x384.png", 41 | "sizes": "384x384", 42 | "type": "image/png" 43 | }, 44 | { 45 | "src": "./gitbook/icons/icon-512x512.png", 46 | "sizes": "512x512", 47 | "type": "image/png" 48 | } 49 | ] 50 | } 51 | -------------------------------------------------------------------------------- /source/basic/class/example/ObservableValue.js: -------------------------------------------------------------------------------- 1 | class EventEmitter { 2 | constructor() { 3 | // 登録済みのイベントリスナーの状態 4 | this.eventHandlers = []; 5 | } 6 | 7 | // `handler`(コールバック関数)を登録する 8 | addEventListener(handler) { 9 | this.eventHandlers.push(handler); 10 | } 11 | 12 | // 登録済みのイベントリスナーーに対して引数`...args`を渡して呼び出す 13 | emit(...args) { 14 | this.eventHandlers.forEach(handler => { 15 | handler(...args); 16 | }); 17 | } 18 | } 19 | 20 | class ObservableValue extends EventEmitter { 21 | constructor(...args) { 22 | super(...args); 23 | } 24 | 25 | onChange(onChangeHandler) { 26 | this.addEventListener(onChangeHandler); 27 | } 28 | 29 | get value() { 30 | return this._value; 31 | } 32 | 33 | set value(newValue) { 34 | const prevValue = this._value; 35 | if (prevValue === newValue) { 36 | return; 37 | } 38 | this._value = newValue; 39 | this.emit(prevValue, newValue); 40 | } 41 | } 42 | 43 | 44 | const observable = new ObservableValue(); 45 | observable.onChange((prevValue, newValue) => { 46 | console.log(prevValue); // => undefined 47 | console.log(newValue); // => 2 48 | }); 49 | // 新しい値変更する 50 | observable.value = 2; 51 | console.log(observable.value); // => 2 52 | -------------------------------------------------------------------------------- /test/example-es-test.js: -------------------------------------------------------------------------------- 1 | import { globbySync } from "globby"; 2 | import path from "node:path"; 3 | import url from "node:url"; 4 | import { describe, it } from "node:test"; 5 | 6 | const __filename__ = url.fileURLToPath(import.meta.url); 7 | const __dirname = path.dirname(__filename__); 8 | const sourceDir = path.join(__dirname, "..", "source"); 9 | 10 | /** 11 | * ESMのexampleを評価するテスト 12 | * Note: ESMには対応していない 13 | **/ 14 | describe("example:es", function() { 15 | const esmFiles = globbySync([ 16 | `${sourceDir}/use-case/todoapp/**/*-example.js`, // *-example.js 17 | `${sourceDir}/use-case/todoapp/**/*.example.js`, // *.example.js 18 | `${sourceDir}/use-case/nodecli/**/example/**/*.js`, 19 | `!${sourceDir}/**/node_modules{,/**}`, 20 | ]); 21 | esmFiles.forEach(filePath => { 22 | const normalizeFilePath = filePath.replace(sourceDir, ""); 23 | // TODO: doctestはしていないで、読み込んでOKかどうかだけ 24 | it(`example:es ${normalizeFilePath}`, { timeout: 5000 }, async function() { 25 | try { 26 | await import(filePath); 27 | } catch (error) { 28 | // Stack Trace like 29 | console.error(`Dynamic Eval is failed 30 | at doctest (${filePath}:1:1)`); 31 | throw error; 32 | } 33 | }); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /source/use-case/todoapp/form-event/add-todo-item/src/App.js: -------------------------------------------------------------------------------- 1 | import { element, render } from "./view/html-util.js"; 2 | 3 | export class App { 4 | mount() { 5 | const formElement = document.querySelector("#js-form"); 6 | const inputElement = document.querySelector("#js-form-input"); 7 | const containerElement = document.querySelector("#js-todo-list"); 8 | const todoItemCountElement = document.querySelector("#js-todo-count"); 9 | // TodoリストをまとめるList要素 10 | const todoListElement = element`
            `; 11 | // Todoアイテム数 12 | let todoItemCount = 0; 13 | formElement.addEventListener("submit", (event) => { 14 | // 本来のsubmitイベントの動作を止める 15 | event.preventDefault(); 16 | // 追加するTodoアイテムの要素(li要素)を作成する 17 | const todoItemElement = element`
          • ${inputElement.value}
          • `; 18 | // TodoアイテムをtodoListElementに追加する 19 | todoListElement.appendChild(todoItemElement); 20 | // コンテナ要素の中身をTodoリストをまとめるList要素で上書きする 21 | render(todoListElement, containerElement); 22 | // Todoアイテム数を+1し、表示されてるテキストを更新する 23 | todoItemCount += 1; 24 | todoItemCountElement.textContent = `Todoアイテム数: ${todoItemCount}`; 25 | // 入力欄を空文字列にしてリセットする 26 | inputElement.value = ""; 27 | }); 28 | } 29 | } 30 | --------------------------------------------------------------------------------