[{"data":1,"prerenderedAt":10973},["ShallowReactive",2],{"series-page-1":3},{"count":4,"content":5},7,[6,1577,3909,5323,6169,8780,10282],{"id":7,"title":8,"body":9,"category":1560,"createdAt":1562,"description":1563,"extension":1564,"index":187,"meta":1565,"navigation":366,"path":1566,"publish":366,"seo":1567,"series":1568,"seriesTitle":1569,"stem":1570,"tag":1571,"thumbnail":1574,"updatedAt":1575,"__hash__":1576},"series\u002Fseries\u002Frtmp-manager-server-1.md","配信中継サーバ開発1：ローカルのDockerとNginxを用いてGoproからRTMPで送信されたライブ配信映像を再生する",{"type":10,"value":11,"toc":1533},"minimark",[12,19,23,26,30,33,46,50,53,64,72,74,77,80,83,86,91,94,97,109,112,123,127,130,134,142,146,149,164,174,177,197,208,213,335,338,341,346,349,648,655,658,739,745,748,755,963,966,969,975,1378,1384,1387,1393,1396,1400,1404,1407,1418,1421,1424,1427,1439,1447,1453,1462,1465,1468,1471,1475,1481,1485,1492,1495,1498,1501,1504,1507,1529],[13,14,18],"div",{"className":15},[16,17],"alert","alert-info","\nこの記事はもともと[https:\u002F\u002Fjun-app.com\u002Farticles\u002Frtmp-docker-local](https:\u002F\u002Fjun-app.com\u002Farticles\u002Frtmp-docker-local) にて掲載していました。この記事に関連する内容をシリーズ化するため、このURLに移動しました。\n",[20,21,22],"p",{},"こんにちはjunです。今回の記事は一風変わって物理世界をちょっと扱います。いつもはPC内で行えることばかりやっていますが、今回はGoproをつかってタイトルの通りライブ映像を再生したいと思います。RTMPサーバーをローカルで作成し、そこに送信できるかのテストをしてみたかった感じです。",[20,24,25],{},"ローカルPCのDockerにNignxをインストールしてRTMPの設定をしてブラウザから見れるようにします。この記事ではまずなぜ、このようなことを始めたいのかという理由から述べます。実装内容をさっさと知りたい方は「RTMPサーバー実装」へ移動してください。",[27,28,29],"h2",{"id":29},"事の経緯",[20,31,32],{},"経緯としては私がyoutubeライブをやりたくなったからです笑。単純にyoutubeライブをやりたいだけならば、さっさとwebカメラからやればいいのですが、私はサイクリングが趣味でなのでその風景をライブしたいと思っていました。持っているアクションカメラがGoproであり、一応公式のスマホアプリを使用することでyoutubeライブをすることができるのですが以下の懸念がありました。",[34,35,36,40,43],"ul",{},[37,38,39],"li",{},"Gopro、スマホアプリなどカメラ側の不具合によるライブの中断",[37,41,42],{},"柔軟なカメラ切り替えを行える環境の準備",[37,44,45],{},"スマホからのyoutubeライブ配信は登録者が50人必要（１番の障壁です笑）",[47,48,49],"h3",{"id":49},"カメラ側によるライブの中断",[20,51,52],{},"Goproは簡単にライブ配信ができるのですが、以下の様なカメラ・スマホ側の要因によってライブが中断される可能性があります。",[34,54,55,58,61],{},[37,56,57],{},"Goproの熱暴走",[37,59,60],{},"トンネルなどでwi-fi・ネットワーク切断",[37,62,63],{},"カメラ、スマホの電池切れ",[20,65,66,67,71],{},"本当に中断されるかは ",[68,69,70],"strong",{},"未検証"," ですが可能性があるのでこれを避けるためには「Gopro側からライブの設定を制御するのでなく、Goproからは音声と映像を取得するのみで配信は別環境で行う」ことがベストな気がします。",[47,73,42],{"id":42},[20,75,76],{},"ライブ配信では映しっぱなしでもいいのですが、撮影禁止の箇所だったり環境によって映像のON\u002FOFFを切り替えたいと思っています。Goproアプリは確かにライブ配信は簡単なのですが、より細かい設定や制御は実装されていません。",[47,78,79],{"id":79},"登録者が50人必要",[20,81,82],{},"スマホ通じてライブ配信をする場合はどうやら50人が必要でしたが、RTMPやPCからであれば0人でもOKでした。",[20,84,85],{},"登録者的な問題だったり、より細かい制御を実装するために以下のような構成を考えた結果、まずは自前RTMPサーバーへの映像の送信と再生ができるかをテストしてみたかった次第です。最終的にはRTMPサーバーから映像を取得してyoutubeに流します。サーバーでは映像の表示制御やyoutubeのコメント読み上げなど配信に関係する機能を管理するものとします。",[87,88],"image-render",{":src":89,":width":90},"'rtmp-docker-local\u002Ffig.png'","'100%'",[27,92,93],{"id":93},"実装概要",[20,95,96],{},"この記事での目標は",[98,99,100,103,106],"ol",{},[37,101,102],{},"Goproからの映像をアプリを通じてコンテナ内のWebサーバーにRTMP通信で映像を送信。",[37,104,105],{},"映像をドキュメントルートへ配置。",[37,107,108],{},"webブラウザからアクセスして映像を見れうようにする。",[20,110,111],{},"まずはRTMPサーバーの実装を行います。使用しているOSはmacOs Catalina 10.15.5でdockerは3.5.2です。実装の手順としては",[98,113,114,117,120],{},[37,115,116],{},"Dockerを用いてRTMPとwebが可能なNginxを構築する。",[37,118,119],{},"RTMPの設定をする。",[37,121,122],{},"web側でHLSにて映像をvideoタグを用いて再生する。",[47,124,126],{"id":125},"rtmpとは","RTMPとは？",[20,128,129],{},"ここまで出ているRTMPとはReal Time Messaging ProtocolのことでTCP上のプロトコルで映像、音声、データを細かいフラグメントに分けてストリーミング送信ができます。今回のようなリアルタイムに映像を撮影してサーバーに送信する際にも利用されます。",[47,131,133],{"id":132},"なぜnginx","なぜNginx？",[20,135,136,137,141],{},"NginxではRTMPモジュールというのがあり、インストールしてconfファイルに少し記述するだけで1935ポートに",[138,139,140],"code",{},"rtmp:\u002F\u002Fexample.com\u002Flive","のようなURLでサーバーに送信できます。",[27,143,145],{"id":144},"rtmpサーバー実装","RTMPサーバー実装",[47,147,148],{"id":148},"docker-composeの作成",[20,150,151,152,155,156,159,160,163],{},"では早速やっていきましょう。",[138,153,154],{},"rtmptest","みたいな適当なディレクトリを作成して、",[138,157,158],{},"Dockerfile","と",[138,161,162],{},"docker-compose.yml","ファイルを作成します。また、webとconfファイルを格納してボリュームしておくディレクトリも作成します。",[165,166,171],"pre",{"className":167,"code":169,"language":170},[168],"language-text","mkdir rtmptest\ncd rtmptest\n\ntouch Dockerfile docker-compose.yml\nmkdir html conf\n","text",[138,172,169],{"__ignoreMap":173},"",[20,175,176],{},"Dockerfileは以下のようにしておきます。",[165,178,181],{"className":179,"code":180,"language":158,"meta":173,"style":173},"language-Dockerfile shiki shiki-themes material-theme-ocean","FROM tiangolo\u002Fnginx-rtmp\nVOLUME [\"\u002Fusr\u002Fshare\u002Fnginx\u002Fhtml\",\"\u002Fetc\u002Fnginx\"]\n",[138,182,183,191],{"__ignoreMap":173},[184,185,188],"span",{"class":186,"line":187},"line",1,[184,189,190],{},"FROM tiangolo\u002Fnginx-rtmp\n",[184,192,194],{"class":186,"line":193},2,[184,195,196],{},"VOLUME [\"\u002Fusr\u002Fshare\u002Fnginx\u002Fhtml\",\"\u002Fetc\u002Fnginx\"]\n",[20,198,199,200,207],{},"今回は",[201,202,206],"a",{"href":203,"rel":204},"https:\u002F\u002Fhub.docker.com\u002Fr\u002Ftiangolo\u002Fnginx-rtmp\u002F",[205],"nofollow","rtmpの設定があるnginxのイメージ","を利用します。",[20,209,210,212],{},[138,211,162],{},"は以下の通りです。",[165,214,219],{"className":215,"code":216,"filename":217,"language":218,"meta":173,"style":173},"language-yaml shiki shiki-themes material-theme-ocean","version: '2'\nservices:\n  app:\n    build: .\n    ports:\n      - \"8085:80\"\n      - \"1935:1935\"\n    volumes:\n      -  .\u002Fhtml:\u002Fusr\u002Fshare\u002Fnginx\u002Fhtml\n      -  .\u002Fconf\u002Fnginx.conf:\u002Fetc\u002Fnginx\u002Fnginx.conf\n      -  .\u002Fconf\u002Fdefault.conf:\u002Fetc\u002Fnginx\u002Fconf.d\u002Fdefault.conf\n","docke-compose.yml","yaml",[138,220,221,241,249,257,269,277,292,303,311,319,327],{"__ignoreMap":173},[184,222,223,227,231,234,238],{"class":186,"line":187},[184,224,226],{"class":225},"s-wAU","version",[184,228,230],{"class":229},"sAklC",":",[184,232,233],{"class":229}," '",[184,235,237],{"class":236},"sfyAc","2",[184,239,240],{"class":229},"'\n",[184,242,243,246],{"class":186,"line":193},[184,244,245],{"class":225},"services",[184,247,248],{"class":229},":\n",[184,250,252,255],{"class":186,"line":251},3,[184,253,254],{"class":225},"  app",[184,256,248],{"class":229},[184,258,260,263,265],{"class":186,"line":259},4,[184,261,262],{"class":225},"    build",[184,264,230],{"class":229},[184,266,268],{"class":267},"sx098"," .\n",[184,270,272,275],{"class":186,"line":271},5,[184,273,274],{"class":225},"    ports",[184,276,248],{"class":229},[184,278,280,283,286,289],{"class":186,"line":279},6,[184,281,282],{"class":229},"      -",[184,284,285],{"class":229}," \"",[184,287,288],{"class":236},"8085:80",[184,290,291],{"class":229},"\"\n",[184,293,294,296,298,301],{"class":186,"line":4},[184,295,282],{"class":229},[184,297,285],{"class":229},[184,299,300],{"class":236},"1935:1935",[184,302,291],{"class":229},[184,304,306,309],{"class":186,"line":305},8,[184,307,308],{"class":225},"    volumes",[184,310,248],{"class":229},[184,312,314,316],{"class":186,"line":313},9,[184,315,282],{"class":229},[184,317,318],{"class":236},"  .\u002Fhtml:\u002Fusr\u002Fshare\u002Fnginx\u002Fhtml\n",[184,320,322,324],{"class":186,"line":321},10,[184,323,282],{"class":229},[184,325,326],{"class":236},"  .\u002Fconf\u002Fnginx.conf:\u002Fetc\u002Fnginx\u002Fnginx.conf\n",[184,328,330,332],{"class":186,"line":329},11,[184,331,282],{"class":229},[184,333,334],{"class":236},"  .\u002Fconf\u002Fdefault.conf:\u002Fetc\u002Fnginx\u002Fconf.d\u002Fdefault.conf\n",[47,336,337],{"id":337},"confファイル調整",[20,339,340],{},"nginxのconfファイルを調整してRTMPとwebアクセスができるようにします。",[342,343,345],"h4",{"id":344},"rtmp","RTMP",[20,347,348],{},"RTMPはnginx.confというapacheで言うhttp.confに以下のように記述します。",[165,350,355],{"className":351,"code":352,"filename":353,"language":354,"meta":173,"style":173},"language-conf shiki shiki-themes material-theme-ocean","worker_processes  auto;\n\nerror_log  \u002Fvar\u002Flog\u002Fnginx\u002Ferror.log notice;\npid        \u002Fvar\u002Frun\u002Fnginx.pid;\n\n\nevents {\n    worker_connections  1024;\n}\n\nrtmp_auto_push on;\n\nrtmp {\n    server {\n        listen 1935;\n        listen [::]:1935 ipv6only=on;\n        access_log \u002Fvar\u002Flog\u002Frtmp_access.log;\n        chunk_size 4096;\n        timeout 10s;\n\n        application live {\n            live on;\n\n            # HLSの記述欄\n            hls on;\n            # ここに映像ファイルが配置される\n            hls_path \u002Fusr\u002Fshare\u002Fnginx\u002Fhtml\u002Fhls;\n            hls_fragment 10s;\n        }\n    }\n}\n\n\n\nhttp {\n    include       \u002Fetc\u002Fnginx\u002Fmime.types;\n    default_type  application\u002Foctet-stream;\n\n    log_format  main  '$remote_addr - $remote_user [$time_local] \"$request\" '\n                      '$status $body_bytes_sent \"$http_referer\" '\n                      '\"$http_user_agent\" \"$http_x_forwarded_for\"';\n\n    access_log  \u002Fvar\u002Flog\u002Fnginx\u002Faccess.log  main;\n\n    sendfile        on;\n    #tcp_nopush     on;\n\n    keepalive_timeout  65;\n\n    #gzip  on;\n\n    include \u002Fetc\u002Fnginx\u002Fconf.d\u002F*.conf;\n}\n","conf\u002Fnginx.conf","conf",[138,356,357,362,368,373,378,382,386,391,396,401,405,410,415,421,427,433,439,445,451,457,462,468,474,479,485,491,497,503,509,515,521,526,531,536,541,547,553,559,564,570,576,582,587,593,598,604,610,615,621,626,632,637,643],{"__ignoreMap":173},[184,358,359],{"class":186,"line":187},[184,360,361],{},"worker_processes  auto;\n",[184,363,364],{"class":186,"line":193},[184,365,367],{"emptyLinePlaceholder":366},true,"\n",[184,369,370],{"class":186,"line":251},[184,371,372],{},"error_log  \u002Fvar\u002Flog\u002Fnginx\u002Ferror.log notice;\n",[184,374,375],{"class":186,"line":259},[184,376,377],{},"pid        \u002Fvar\u002Frun\u002Fnginx.pid;\n",[184,379,380],{"class":186,"line":271},[184,381,367],{"emptyLinePlaceholder":366},[184,383,384],{"class":186,"line":279},[184,385,367],{"emptyLinePlaceholder":366},[184,387,388],{"class":186,"line":4},[184,389,390],{},"events {\n",[184,392,393],{"class":186,"line":305},[184,394,395],{},"    worker_connections  1024;\n",[184,397,398],{"class":186,"line":313},[184,399,400],{},"}\n",[184,402,403],{"class":186,"line":321},[184,404,367],{"emptyLinePlaceholder":366},[184,406,407],{"class":186,"line":329},[184,408,409],{},"rtmp_auto_push on;\n",[184,411,413],{"class":186,"line":412},12,[184,414,367],{"emptyLinePlaceholder":366},[184,416,418],{"class":186,"line":417},13,[184,419,420],{},"rtmp {\n",[184,422,424],{"class":186,"line":423},14,[184,425,426],{},"    server {\n",[184,428,430],{"class":186,"line":429},15,[184,431,432],{},"        listen 1935;\n",[184,434,436],{"class":186,"line":435},16,[184,437,438],{},"        listen [::]:1935 ipv6only=on;\n",[184,440,442],{"class":186,"line":441},17,[184,443,444],{},"        access_log \u002Fvar\u002Flog\u002Frtmp_access.log;\n",[184,446,448],{"class":186,"line":447},18,[184,449,450],{},"        chunk_size 4096;\n",[184,452,454],{"class":186,"line":453},19,[184,455,456],{},"        timeout 10s;\n",[184,458,460],{"class":186,"line":459},20,[184,461,367],{"emptyLinePlaceholder":366},[184,463,465],{"class":186,"line":464},21,[184,466,467],{},"        application live {\n",[184,469,471],{"class":186,"line":470},22,[184,472,473],{},"            live on;\n",[184,475,477],{"class":186,"line":476},23,[184,478,367],{"emptyLinePlaceholder":366},[184,480,482],{"class":186,"line":481},24,[184,483,484],{},"            # HLSの記述欄\n",[184,486,488],{"class":186,"line":487},25,[184,489,490],{},"            hls on;\n",[184,492,494],{"class":186,"line":493},26,[184,495,496],{},"            # ここに映像ファイルが配置される\n",[184,498,500],{"class":186,"line":499},27,[184,501,502],{},"            hls_path \u002Fusr\u002Fshare\u002Fnginx\u002Fhtml\u002Fhls;\n",[184,504,506],{"class":186,"line":505},28,[184,507,508],{},"            hls_fragment 10s;\n",[184,510,512],{"class":186,"line":511},29,[184,513,514],{},"        }\n",[184,516,518],{"class":186,"line":517},30,[184,519,520],{},"    }\n",[184,522,524],{"class":186,"line":523},31,[184,525,400],{},[184,527,529],{"class":186,"line":528},32,[184,530,367],{"emptyLinePlaceholder":366},[184,532,534],{"class":186,"line":533},33,[184,535,367],{"emptyLinePlaceholder":366},[184,537,539],{"class":186,"line":538},34,[184,540,367],{"emptyLinePlaceholder":366},[184,542,544],{"class":186,"line":543},35,[184,545,546],{},"http {\n",[184,548,550],{"class":186,"line":549},36,[184,551,552],{},"    include       \u002Fetc\u002Fnginx\u002Fmime.types;\n",[184,554,556],{"class":186,"line":555},37,[184,557,558],{},"    default_type  application\u002Foctet-stream;\n",[184,560,562],{"class":186,"line":561},38,[184,563,367],{"emptyLinePlaceholder":366},[184,565,567],{"class":186,"line":566},39,[184,568,569],{},"    log_format  main  '$remote_addr - $remote_user [$time_local] \"$request\" '\n",[184,571,573],{"class":186,"line":572},40,[184,574,575],{},"                      '$status $body_bytes_sent \"$http_referer\" '\n",[184,577,579],{"class":186,"line":578},41,[184,580,581],{},"                      '\"$http_user_agent\" \"$http_x_forwarded_for\"';\n",[184,583,585],{"class":186,"line":584},42,[184,586,367],{"emptyLinePlaceholder":366},[184,588,590],{"class":186,"line":589},43,[184,591,592],{},"    access_log  \u002Fvar\u002Flog\u002Fnginx\u002Faccess.log  main;\n",[184,594,596],{"class":186,"line":595},44,[184,597,367],{"emptyLinePlaceholder":366},[184,599,601],{"class":186,"line":600},45,[184,602,603],{},"    sendfile        on;\n",[184,605,607],{"class":186,"line":606},46,[184,608,609],{},"    #tcp_nopush     on;\n",[184,611,613],{"class":186,"line":612},47,[184,614,367],{"emptyLinePlaceholder":366},[184,616,618],{"class":186,"line":617},48,[184,619,620],{},"    keepalive_timeout  65;\n",[184,622,624],{"class":186,"line":623},49,[184,625,367],{"emptyLinePlaceholder":366},[184,627,629],{"class":186,"line":628},50,[184,630,631],{},"    #gzip  on;\n",[184,633,635],{"class":186,"line":634},51,[184,636,367],{"emptyLinePlaceholder":366},[184,638,640],{"class":186,"line":639},52,[184,641,642],{},"    include \u002Fetc\u002Fnginx\u002Fconf.d\u002F*.conf;\n",[184,644,646],{"class":186,"line":645},53,[184,647,400],{},[20,649,650,651,654],{},"このファイルはコンテナ内の",[138,652,653],{},"nginx.conf","にボリュームします。tiangolo\u002Fnginx-rtmpのイメージで生成されるnginx.confはRTMPのために最低限の記述しかないので上記のようにします。",[20,656,657],{},"RTMPでは以下の設定です。",[165,659,661],{"className":351,"code":660,"language":354,"meta":173,"style":173},"rtmp {\n    server {\n        listen 1935;\n        listen [::]:1935 ipv6only=on;\n        access_log \u002Fvar\u002Flog\u002Frtmp_access.log;\n        chunk_size 4096;\n        timeout 10s;\n\n        application live {\n            live on;\n\n            # HLSの記述欄\n            hls on;\n            # ここに映像ファイルが配置される\n            hls_path \u002Fusr\u002Fshare\u002Fnginx\u002Fhtml\u002Fhls;\n            hls_fragment 10s;\n        }\n    }\n}\n",[138,662,663,667,671,675,679,683,687,691,695,699,703,707,711,715,719,723,727,731,735],{"__ignoreMap":173},[184,664,665],{"class":186,"line":187},[184,666,420],{},[184,668,669],{"class":186,"line":193},[184,670,426],{},[184,672,673],{"class":186,"line":251},[184,674,432],{},[184,676,677],{"class":186,"line":259},[184,678,438],{},[184,680,681],{"class":186,"line":271},[184,682,444],{},[184,684,685],{"class":186,"line":279},[184,686,450],{},[184,688,689],{"class":186,"line":4},[184,690,456],{},[184,692,693],{"class":186,"line":305},[184,694,367],{"emptyLinePlaceholder":366},[184,696,697],{"class":186,"line":313},[184,698,467],{},[184,700,701],{"class":186,"line":321},[184,702,473],{},[184,704,705],{"class":186,"line":329},[184,706,367],{"emptyLinePlaceholder":366},[184,708,709],{"class":186,"line":412},[184,710,484],{},[184,712,713],{"class":186,"line":417},[184,714,490],{},[184,716,717],{"class":186,"line":423},[184,718,496],{},[184,720,721],{"class":186,"line":429},[184,722,502],{},[184,724,725],{"class":186,"line":435},[184,726,508],{},[184,728,729],{"class":186,"line":441},[184,730,514],{},[184,732,733],{"class":186,"line":447},[184,734,520],{},[184,736,737],{"class":186,"line":453},[184,738,400],{},[20,740,741,742,744],{},"ここでは1935ポートでRTMPをリッスンし、また",[138,743,140],{},"というURLでアップロードできるようにしています。",[342,746,747],{"id":747},"web",[20,749,750,751,754],{},"webは以下のようにしておきます。ドキュメントルートは",[138,752,753],{},"\u002Fusr\u002Fshare\u002Fnginx\u002Fhtml","です。",[165,756,758],{"className":351,"code":757,"filename":353,"language":354,"meta":173,"style":173},"server {\n    listen       80;\n    listen  [::]:80;\n    server_name  localhost;\n\n    #access_log  \u002Fvar\u002Flog\u002Fnginx\u002Fhost.access.log  main;\n\n    location \u002F {\n        root   \u002Fusr\u002Fshare\u002Fnginx\u002Fhtml;\n        index  index.html index.htm;\n    }\n\n    #error_page  404              \u002F404.html;\n\n    # redirect server error pages to the static page \u002F50x.html\n    #\n    error_page   500 502 503 504  \u002F50x.html;\n    location = \u002F50x.html {\n        root   \u002Fusr\u002Fshare\u002Fnginx\u002Fhtml;\n    }\n\n    # proxy the PHP scripts to Apache listening on 127.0.0.1:80\n    #\n    #location ~ \\.php$ {\n    #    proxy_pass   http:\u002F\u002F127.0.0.1;\n    #}\n\n    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000\n    #\n    #location ~ \\.php$ {\n    #    root           html;\n    #    fastcgi_pass   127.0.0.1:9000;\n    #    fastcgi_index  index.php;\n    #    fastcgi_param  SCRIPT_FILENAME  \u002Fscripts$fastcgi_script_name;\n    #    include        fastcgi_params;\n    #}\n\n    # deny access to .htaccess files, if Apache's document root\n    # concurs with nginx's one\n    #\n    #location ~ \u002F\\.ht {\n    #    deny  all;\n    #}\n}\n",[138,759,760,765,770,775,780,784,789,793,798,803,808,812,816,821,825,830,835,840,845,849,853,857,862,866,871,876,881,885,890,894,898,903,908,913,918,923,927,931,936,941,945,950,955,959],{"__ignoreMap":173},[184,761,762],{"class":186,"line":187},[184,763,764],{},"server {\n",[184,766,767],{"class":186,"line":193},[184,768,769],{},"    listen       80;\n",[184,771,772],{"class":186,"line":251},[184,773,774],{},"    listen  [::]:80;\n",[184,776,777],{"class":186,"line":259},[184,778,779],{},"    server_name  localhost;\n",[184,781,782],{"class":186,"line":271},[184,783,367],{"emptyLinePlaceholder":366},[184,785,786],{"class":186,"line":279},[184,787,788],{},"    #access_log  \u002Fvar\u002Flog\u002Fnginx\u002Fhost.access.log  main;\n",[184,790,791],{"class":186,"line":4},[184,792,367],{"emptyLinePlaceholder":366},[184,794,795],{"class":186,"line":305},[184,796,797],{},"    location \u002F {\n",[184,799,800],{"class":186,"line":313},[184,801,802],{},"        root   \u002Fusr\u002Fshare\u002Fnginx\u002Fhtml;\n",[184,804,805],{"class":186,"line":321},[184,806,807],{},"        index  index.html index.htm;\n",[184,809,810],{"class":186,"line":329},[184,811,520],{},[184,813,814],{"class":186,"line":412},[184,815,367],{"emptyLinePlaceholder":366},[184,817,818],{"class":186,"line":417},[184,819,820],{},"    #error_page  404              \u002F404.html;\n",[184,822,823],{"class":186,"line":423},[184,824,367],{"emptyLinePlaceholder":366},[184,826,827],{"class":186,"line":429},[184,828,829],{},"    # redirect server error pages to the static page \u002F50x.html\n",[184,831,832],{"class":186,"line":435},[184,833,834],{},"    #\n",[184,836,837],{"class":186,"line":441},[184,838,839],{},"    error_page   500 502 503 504  \u002F50x.html;\n",[184,841,842],{"class":186,"line":447},[184,843,844],{},"    location = \u002F50x.html {\n",[184,846,847],{"class":186,"line":453},[184,848,802],{},[184,850,851],{"class":186,"line":459},[184,852,520],{},[184,854,855],{"class":186,"line":464},[184,856,367],{"emptyLinePlaceholder":366},[184,858,859],{"class":186,"line":470},[184,860,861],{},"    # proxy the PHP scripts to Apache listening on 127.0.0.1:80\n",[184,863,864],{"class":186,"line":476},[184,865,834],{},[184,867,868],{"class":186,"line":481},[184,869,870],{},"    #location ~ \\.php$ {\n",[184,872,873],{"class":186,"line":487},[184,874,875],{},"    #    proxy_pass   http:\u002F\u002F127.0.0.1;\n",[184,877,878],{"class":186,"line":493},[184,879,880],{},"    #}\n",[184,882,883],{"class":186,"line":499},[184,884,367],{"emptyLinePlaceholder":366},[184,886,887],{"class":186,"line":505},[184,888,889],{},"    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000\n",[184,891,892],{"class":186,"line":511},[184,893,834],{},[184,895,896],{"class":186,"line":517},[184,897,870],{},[184,899,900],{"class":186,"line":523},[184,901,902],{},"    #    root           html;\n",[184,904,905],{"class":186,"line":528},[184,906,907],{},"    #    fastcgi_pass   127.0.0.1:9000;\n",[184,909,910],{"class":186,"line":533},[184,911,912],{},"    #    fastcgi_index  index.php;\n",[184,914,915],{"class":186,"line":538},[184,916,917],{},"    #    fastcgi_param  SCRIPT_FILENAME  \u002Fscripts$fastcgi_script_name;\n",[184,919,920],{"class":186,"line":543},[184,921,922],{},"    #    include        fastcgi_params;\n",[184,924,925],{"class":186,"line":549},[184,926,880],{},[184,928,929],{"class":186,"line":555},[184,930,367],{"emptyLinePlaceholder":366},[184,932,933],{"class":186,"line":561},[184,934,935],{},"    # deny access to .htaccess files, if Apache's document root\n",[184,937,938],{"class":186,"line":566},[184,939,940],{},"    # concurs with nginx's one\n",[184,942,943],{"class":186,"line":572},[184,944,834],{},[184,946,947],{"class":186,"line":578},[184,948,949],{},"    #location ~ \u002F\\.ht {\n",[184,951,952],{"class":186,"line":584},[184,953,954],{},"    #    deny  all;\n",[184,956,957],{"class":186,"line":589},[184,958,880],{},[184,960,961],{"class":186,"line":595},[184,962,400],{},[20,964,965],{},"RTMPを含むNginxの設定はこれでOKです。",[47,967,968],{"id":968},"web側実装",[20,970,971,974],{},[138,972,973],{},"html\u002Ftest.html","として以下のように作成します。",[165,976,981],{"className":977,"code":978,"filename":979,"language":980,"meta":173,"style":173},"language-html shiki shiki-themes material-theme-ocean","\u003C!DOCTYPE html>\n\u003Chtml>\n\n\u003Chead>\n  \u003Cmeta charset=\"utf-8\">\n  \u003Ctitle>MediaElement\u003C\u002Ftitle>\n  \u003C!-- MediaElement style -->\n  \u003Clink rel=\"stylesheet\" href=\"\u002F\u002Fcdnjs.cloudflare.com\u002Fajax\u002Flibs\u002Fmediaelement\u002F4.2.9\u002Fmediaelementplayer.css\" \u002F>\n\u003C\u002Fhead>\n\n\u003Cbody>\n  \u003C!-- MediaElement -->\n  \u003Cscript src=\"\u002F\u002Fcdnjs.cloudflare.com\u002Fajax\u002Flibs\u002Fmediaelement\u002F4.2.9\u002Fmediaelement-and-player.js\">\u003C\u002Fscript>\n\n  \u003Cvideo id=\"player\" width=\"640\" height=\"360\">\n\u003C\u002Fbody>\n\u003Cscript type=\"text\u002Fjavascript\">\n\n      var player = new MediaElementPlayer('player', {\n        success: function(mediaElement, originalNode) {\n          console.log(\"Player initialised\");\n        }\n      });\n        player.setSrc(\"hls\u002F.m3u8\");\n\u003C\u002Fscript>\n\n\u003C\u002Fhtml>\n","test.html","html",[138,982,983,998,1007,1011,1020,1044,1065,1071,1105,1113,1117,1126,1131,1157,1161,1206,1214,1234,1238,1271,1297,1322,1326,1335,1358,1366,1370],{"__ignoreMap":173},[184,984,985,988,991,995],{"class":186,"line":187},[184,986,987],{"class":229},"\u003C!",[184,989,990],{"class":225},"DOCTYPE",[184,992,994],{"class":993},"sJ14y"," html",[184,996,997],{"class":229},">\n",[184,999,1000,1003,1005],{"class":186,"line":193},[184,1001,1002],{"class":229},"\u003C",[184,1004,980],{"class":225},[184,1006,997],{"class":229},[184,1008,1009],{"class":186,"line":251},[184,1010,367],{"emptyLinePlaceholder":366},[184,1012,1013,1015,1018],{"class":186,"line":259},[184,1014,1002],{"class":229},[184,1016,1017],{"class":225},"head",[184,1019,997],{"class":229},[184,1021,1022,1025,1028,1031,1034,1037,1040,1042],{"class":186,"line":271},[184,1023,1024],{"class":229},"  \u003C",[184,1026,1027],{"class":225},"meta",[184,1029,1030],{"class":993}," charset",[184,1032,1033],{"class":229},"=",[184,1035,1036],{"class":229},"\"",[184,1038,1039],{"class":236},"utf-8",[184,1041,1036],{"class":229},[184,1043,997],{"class":229},[184,1045,1046,1048,1051,1054,1058,1061,1063],{"class":186,"line":279},[184,1047,1024],{"class":229},[184,1049,1050],{"class":225},"title",[184,1052,1053],{"class":229},">",[184,1055,1057],{"class":1056},"s0W1g","MediaElement",[184,1059,1060],{"class":229},"\u003C\u002F",[184,1062,1050],{"class":225},[184,1064,997],{"class":229},[184,1066,1067],{"class":186,"line":4},[184,1068,1070],{"class":1069},"sC9rS","  \u003C!-- MediaElement style -->\n",[184,1072,1073,1075,1078,1081,1083,1085,1088,1090,1093,1095,1097,1100,1102],{"class":186,"line":305},[184,1074,1024],{"class":229},[184,1076,1077],{"class":225},"link",[184,1079,1080],{"class":993}," rel",[184,1082,1033],{"class":229},[184,1084,1036],{"class":229},[184,1086,1087],{"class":236},"stylesheet",[184,1089,1036],{"class":229},[184,1091,1092],{"class":993}," href",[184,1094,1033],{"class":229},[184,1096,1036],{"class":229},[184,1098,1099],{"class":236},"\u002F\u002Fcdnjs.cloudflare.com\u002Fajax\u002Flibs\u002Fmediaelement\u002F4.2.9\u002Fmediaelementplayer.css",[184,1101,1036],{"class":229},[184,1103,1104],{"class":229}," \u002F>\n",[184,1106,1107,1109,1111],{"class":186,"line":313},[184,1108,1060],{"class":229},[184,1110,1017],{"class":225},[184,1112,997],{"class":229},[184,1114,1115],{"class":186,"line":321},[184,1116,367],{"emptyLinePlaceholder":366},[184,1118,1119,1121,1124],{"class":186,"line":329},[184,1120,1002],{"class":229},[184,1122,1123],{"class":225},"body",[184,1125,997],{"class":229},[184,1127,1128],{"class":186,"line":412},[184,1129,1130],{"class":1069},"  \u003C!-- MediaElement -->\n",[184,1132,1133,1135,1138,1141,1143,1145,1148,1150,1153,1155],{"class":186,"line":417},[184,1134,1024],{"class":229},[184,1136,1137],{"class":225},"script",[184,1139,1140],{"class":993}," src",[184,1142,1033],{"class":229},[184,1144,1036],{"class":229},[184,1146,1147],{"class":236},"\u002F\u002Fcdnjs.cloudflare.com\u002Fajax\u002Flibs\u002Fmediaelement\u002F4.2.9\u002Fmediaelement-and-player.js",[184,1149,1036],{"class":229},[184,1151,1152],{"class":229},">\u003C\u002F",[184,1154,1137],{"class":225},[184,1156,997],{"class":229},[184,1158,1159],{"class":186,"line":423},[184,1160,367],{"emptyLinePlaceholder":366},[184,1162,1163,1165,1168,1171,1173,1175,1178,1180,1183,1185,1187,1190,1192,1195,1197,1199,1202,1204],{"class":186,"line":429},[184,1164,1024],{"class":229},[184,1166,1167],{"class":225},"video",[184,1169,1170],{"class":993}," id",[184,1172,1033],{"class":229},[184,1174,1036],{"class":229},[184,1176,1177],{"class":236},"player",[184,1179,1036],{"class":229},[184,1181,1182],{"class":993}," width",[184,1184,1033],{"class":229},[184,1186,1036],{"class":229},[184,1188,1189],{"class":236},"640",[184,1191,1036],{"class":229},[184,1193,1194],{"class":993}," height",[184,1196,1033],{"class":229},[184,1198,1036],{"class":229},[184,1200,1201],{"class":236},"360",[184,1203,1036],{"class":229},[184,1205,997],{"class":229},[184,1207,1208,1210,1212],{"class":186,"line":435},[184,1209,1060],{"class":229},[184,1211,1123],{"class":225},[184,1213,997],{"class":229},[184,1215,1216,1218,1220,1223,1225,1227,1230,1232],{"class":186,"line":441},[184,1217,1002],{"class":229},[184,1219,1137],{"class":225},[184,1221,1222],{"class":993}," type",[184,1224,1033],{"class":229},[184,1226,1036],{"class":229},[184,1228,1229],{"class":236},"text\u002Fjavascript",[184,1231,1036],{"class":229},[184,1233,997],{"class":229},[184,1235,1236],{"class":186,"line":447},[184,1237,367],{"emptyLinePlaceholder":366},[184,1239,1240,1243,1246,1248,1251,1255,1258,1261,1263,1265,1268],{"class":186,"line":453},[184,1241,1242],{"class":993},"      var",[184,1244,1245],{"class":1056}," player ",[184,1247,1033],{"class":229},[184,1249,1250],{"class":229}," new",[184,1252,1254],{"class":1253},"sdLwU"," MediaElementPlayer",[184,1256,1257],{"class":1056},"(",[184,1259,1260],{"class":229},"'",[184,1262,1177],{"class":236},[184,1264,1260],{"class":229},[184,1266,1267],{"class":229},",",[184,1269,1270],{"class":229}," {\n",[184,1272,1273,1276,1278,1281,1283,1287,1289,1292,1295],{"class":186,"line":459},[184,1274,1275],{"class":1253},"        success",[184,1277,230],{"class":229},[184,1279,1280],{"class":993}," function",[184,1282,1257],{"class":229},[184,1284,1286],{"class":1285},"s7ZW3","mediaElement",[184,1288,1267],{"class":229},[184,1290,1291],{"class":1285}," originalNode",[184,1293,1294],{"class":229},")",[184,1296,1270],{"class":229},[184,1298,1299,1302,1305,1308,1310,1312,1315,1317,1319],{"class":186,"line":464},[184,1300,1301],{"class":1056},"          console",[184,1303,1304],{"class":229},".",[184,1306,1307],{"class":1253},"log",[184,1309,1257],{"class":225},[184,1311,1036],{"class":229},[184,1313,1314],{"class":236},"Player initialised",[184,1316,1036],{"class":229},[184,1318,1294],{"class":225},[184,1320,1321],{"class":229},";\n",[184,1323,1324],{"class":186,"line":470},[184,1325,514],{"class":229},[184,1327,1328,1331,1333],{"class":186,"line":476},[184,1329,1330],{"class":229},"      }",[184,1332,1294],{"class":1056},[184,1334,1321],{"class":229},[184,1336,1337,1340,1342,1345,1347,1349,1352,1354,1356],{"class":186,"line":481},[184,1338,1339],{"class":1056},"        player",[184,1341,1304],{"class":229},[184,1343,1344],{"class":1253},"setSrc",[184,1346,1257],{"class":1056},[184,1348,1036],{"class":229},[184,1350,1351],{"class":236},"hls\u002F.m3u8",[184,1353,1036],{"class":229},[184,1355,1294],{"class":1056},[184,1357,1321],{"class":229},[184,1359,1360,1362,1364],{"class":186,"line":487},[184,1361,1060],{"class":229},[184,1363,1137],{"class":225},[184,1365,997],{"class":229},[184,1367,1368],{"class":186,"line":493},[184,1369,367],{"emptyLinePlaceholder":366},[184,1371,1372,1374,1376],{"class":186,"line":499},[184,1373,1060],{"class":229},[184,1375,980],{"class":225},[184,1377,997],{"class":229},[20,1379,1380,1381,1383],{},"hlsが再生できるようにjsの再生ライブラリを用意します。",[138,1382,1351],{},"については後述します。",[47,1385,1386],{"id":1386},"dokcer起動",[165,1388,1391],{"className":1389,"code":1390,"language":170},[168],"docker-compose up -d\n",[138,1392,1390],{"__ignoreMap":173},[20,1394,1395],{},"を用いてdockerイメージを起動しましょう。",[27,1397,1399],{"id":1398},"配信テスト","配信テスト！",[47,1401,1403],{"id":1402},"macのipを確認","MacのIPを確認",[20,1405,1406],{},"今回のはMac上にRTMPサーバを立てたのでMacのIPを確認しておきます。「システム環境設定」から「ネットワーク」を選択し、Wi-FiからIPアドレスを確認します。このPCは「192.168.0.3」なので",[20,1408,1409,1410,1413,1414,1417],{},"RTMPは",[138,1411,1412],{},"rtmp:\u002F\u002F192.168.0.3\u002Flive","、webは",[138,1415,1416],{},"http:\u002F\u002F192.168.0.3\u002Findex.html","でアクセスできます。",[87,1419],{":src":1420,":width":90},"'rtmp-docker-local\u002Fmac.png'",[47,1422,1423],{"id":1423},"gopro側の設定",[20,1425,1426],{},"Goproとアプリのテザリングなどは省略します。",[20,1428,1429,1434],{},[201,1430,1433],{"href":1431,"rel":1432},"https:\u002F\u002Fapps.apple.com\u002Fjp\u002Fapp\u002Fgopro-quik-%E5%8B%95%E7%94%BB-%E5%86%99%E7%9C%9F%E7%B7%A8%E9%9B%86%E3%82%A2%E3%83%97%E3%83%AA\u002Fid561350520",[205],"公式アプリ（app store）",[201,1435,1438],{"href":1436,"rel":1437},"https:\u002F\u002Fplay.google.com\u002Fstore\u002Fapps\u002Fdetails?id=com.gopro.smarty&hl=ja&gl=US",[205],"公式アプリ（android）",[20,1440,1441,1442],{},"goproとアプリを接続して、「ライブの設定」に移動\n",[87,1443],{":src":1444,":width":1445,":center":1446},"'rtmp-docker-local\u002Fg1.jpg'","'200px'","true",[20,1448,1449,1450],{},"その他、RTMPを選択\n",[87,1451],{":src":1452,":width":1445,":center":1446},"'rtmp-docker-local\u002Fg2.jpg'",[20,1454,1455,1456,1458,1459],{},"Macと同じwi-fiに接続して",[138,1457,1412],{},"をURLに入力\n",[87,1460],{":src":1461,":width":1445,":center":1446},"'rtmp-docker-local\u002Fg3.jpg'",[20,1463,1464],{},"設定が完了したらライブ配信を開始します。",[47,1466,1467],{"id":1467},"サーバーでは",[20,1469,1470],{},"ライブ配信を開始するとRTMPのURLへ動画がアップされていきます。サーバーの方ではドキュメントルートにこの映像のHLSが配置されるようにしたので、マウントの関係上以下のようなディレクトリが発生していると思います。",[87,1472],{":src":1473,":width":1474,":center":1446},"'rtmp-docker-local\u002Fserver.png'","'100px'",[20,1476,1477,1478,1480],{},"先程のHTMLにあった「",[138,1479,1351],{},"」はサーバーにアップロードされた映像の元でもあるファイルを示しています。",[47,1482,1484],{"id":1483},"ブラウザを見てみると","ブラウザを見てみると..",[20,1486,1487,1488,1491],{},"では",[138,1489,1490],{},"http:\u002F\u002Flocalhost:8085\u002Findex.html","でPCから映像を見てみましょう。再生ボタンを押すと以下のようになりました。",[87,1493],{":src":1494,":width":90},"'rtmp-docker-local\u002Fweb.png'",[20,1496,1497],{},"しっかりとGoproが写している映像が表示され、音声も流れました。画面を写していましたが、いろいろカメラを回すと確かに映像・音声も切り替わります。",[20,1499,1500],{},"とりあえずこれでライブ配信映像を自前で実装したサーバに送ることが確認できました。今度はサーバー内での映像をyoutubeのlivestreaming apiなどを通じて実際にライブ映像に流せるようにしてみます。",[27,1502,1503],{"id":1503},"参考",[20,1505,1506],{},"今回参考にした資料です。非常に助かりました。",[34,1508,1509,1516,1523],{},[37,1510,1511],{},[201,1512,1515],{"href":1513,"rel":1514},"https:\u002F\u002Fqiita.com\u002Fkobakazu0429\u002Fitems\u002F33739b83000583c5448e",[205],"docker-nginx-rtmpとiPhoneでお手軽マルチカメラライブストリーミング配信環境の構築",[37,1517,1518],{},[201,1519,1522],{"href":1520,"rel":1521},"https:\u002F\u002Fblog.litus.co.jp\u002F2020\u002F11\u002Fdockerhlswebcentos-nginxffmpegdocker.html",[205],"Dockerコンテナ化させたHLSのwebストリーミング配信環境構築",[37,1524,1525],{},[201,1526,1528],{"href":203,"rel":1527},[205],"Docker image",[1530,1531,1532],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s-wAU, html code.shiki .s-wAU{--shiki-default:#F07178}html pre.shiki code .sAklC, html code.shiki .sAklC{--shiki-default:#89DDFF}html pre.shiki code .sfyAc, html code.shiki .sfyAc{--shiki-default:#C3E88D}html pre.shiki code .sx098, html code.shiki .sx098{--shiki-default:#F78C6C}html pre.shiki code .sJ14y, html code.shiki .sJ14y{--shiki-default:#C792EA}html pre.shiki code .s0W1g, html code.shiki .s0W1g{--shiki-default:#BABED8}html pre.shiki code .sC9rS, html code.shiki .sC9rS{--shiki-default:#464B5D;--shiki-default-font-style:italic}html pre.shiki code .sdLwU, html code.shiki .sdLwU{--shiki-default:#82AAFF}html pre.shiki code .s7ZW3, html code.shiki .s7ZW3{--shiki-default:#BABED8;--shiki-default-font-style:italic}",{"title":173,"searchDepth":251,"depth":251,"links":1534},[1535,1540,1544,1553,1559],{"id":29,"depth":193,"text":29,"children":1536},[1537,1538,1539],{"id":49,"depth":251,"text":49},{"id":42,"depth":251,"text":42},{"id":79,"depth":251,"text":79},{"id":93,"depth":193,"text":93,"children":1541},[1542,1543],{"id":125,"depth":251,"text":126},{"id":132,"depth":251,"text":133},{"id":144,"depth":193,"text":145,"children":1545},[1546,1547,1551,1552],{"id":148,"depth":251,"text":148},{"id":337,"depth":251,"text":337,"children":1548},[1549,1550],{"id":344,"depth":259,"text":345},{"id":747,"depth":259,"text":747},{"id":968,"depth":251,"text":968},{"id":1386,"depth":251,"text":1386},{"id":1398,"depth":193,"text":1399,"children":1554},[1555,1556,1557,1558],{"id":1402,"depth":251,"text":1403},{"id":1423,"depth":251,"text":1423},{"id":1467,"depth":251,"text":1467},{"id":1483,"depth":251,"text":1484},{"id":1503,"depth":193,"text":1503},[1561],"devstack","2022-05-20","ローカルのDockerとNginxを用いてGoproからライブ配信映像をRTMPを再生する","md",{},"\u002Fseries\u002Frtmp-manager-server-1",{"title":8,"description":1563},"rtmp-manager-server","Goproの配信映像を中継してyoutubeへの配信を管理するwebシステムを開発する","series\u002Frtmp-manager-server-1",[1572,1573],"docker","nginx","rtmp-docker-local\u002Fthumbnail.png",null,"_e2Aj1JaZSf-KJSGe6AqjuPlf5YdLmKDjuCXzhFw0dg",{"id":1578,"title":1579,"body":1580,"category":3897,"createdAt":3898,"description":3899,"extension":1564,"index":187,"meta":3900,"navigation":366,"path":3901,"publish":366,"seo":3902,"series":3903,"seriesTitle":3904,"stem":3905,"tag":3906,"thumbnail":3907,"updatedAt":1575,"__hash__":3908},"series\u002Fseries\u002Fvue-laravel-app-1.md","Vue SPA x Laravelでつくる実務パチモンアプリその１：アプリの概要と環境構築",{"type":10,"value":1581,"toc":3873},[1582,1585,1602,1613,1616,1636,1639,1642,1645,1648,1651,1654,1685,1688,1725,1728,1739,1742,1745,1748,1751,1754,1763,1777,1784,1847,1860,1865,1869,1872,1925,1928,1931,1934,1952,2007,2014,2018,2021,2337,2354,2363,2421,2424,2430,2444,2453,2459,2474,2477,2480,2484,2491,2497,2500,2504,2507,2511,2517,2540,2546,2678,2689,2692,2716,2721,2727,2731,2738,2744,2747,2806,2809,2943,2950,2953,3072,3155,3158,3489,3496,3503,3506,3525,3699,3708,3720,3757,3760,3799,3805,3818,3829,3832,3845,3848,3857,3860,3863,3867,3870],[20,1583,1584],{},"こんにちはjunです。仕事で中規模プロジェクトのディレクターとしてアサインされて、半年ほどブログを書けませんでした。ようやく最近時間が出てきたので新しいシリーズ記事を作成しようと思いました。とにかく最近１年はシステムばっかり作っていました。それぞのれプロジェクトでは",[34,1586,1587,1590,1593,1596,1599],{},[37,1588,1589],{},"CRUDなAPI",[37,1591,1592],{},"管理画面の構築",[37,1594,1595],{},"ユーザー権限によるアクセス制限",[37,1597,1598],{},"ログイン・ログアウト",[37,1600,1601],{},"ジョブ・キューを用いた長い処理",[20,1603,1604,1605,1608,1609,1612],{},"など使用するシステムの構築を多く行い、いろいろ学ぶことできました。その中で「CRUDなAPI」を考えた「管理画面」の構築をすることが多くありました。バックエンドは",[138,1606,1607],{},"Laravel","を、フロントエンドは",[138,1610,1611],{},"Vue.js","を使用していました。Vue.jsのおかげで高速にリッチなUIを構築し、システムを開発することができました。",[20,1614,1615],{},"この知見を共有し、まとめるためにも今回は",[34,1617,1618,1621,1624,1627,1630,1633],{},[37,1619,1620],{},"管理画面はVue.js SPA",[37,1622,1623],{},"web APIによるCRUD操作",[37,1625,1626],{},"ルーティング設計",[37,1628,1629],{},"バリデーション",[37,1631,1632],{},"MVCによるアプリケーション設計",[37,1634,1635],{},"laravel\u002Fpermissionを用いたユーザー権限",[20,1637,1638],{},"以上の様な機能を持ったデモアプリを作ろうと思います。Laravelを用いたシステム開発と、Vue.jsによる管理画面フロントエンド開発を解説していきます。",[27,1640,1641],{"id":1641},"作るアプリの概要",[20,1643,1644],{},"今回のアプリはn○teみたいなブログサービスを作ります。CRUDや管理画面の構築をメインに行いたいのでシンプルにいきます。",[47,1646,1647],{"id":1647},"機能概要",[20,1649,1650],{},"ざっくりとした機能概要は以下のとおりです。",[342,1652,1653],{"id":1653},"ユーザー系の特徴",[34,1655,1656,1659,1662,1665,1668,1671],{},[37,1657,1658],{},"利用する際にはユーザーとしてアカウントを作る（今回は全てゲストとする）。",[37,1660,1661],{},"ログインはアドレスとパスワードを使用する。",[37,1663,1664],{},"アプリを利用する一般ユーザーと管理を行う管理ユーザーが存在する。",[37,1666,1667],{},"管理ユーザーは特定の管理用ルートから入る。",[37,1669,1670],{},"退会可能。退会時は関連するデータは削除される。",[37,1672,1673,1674],{},"プロフィールでは以下の項目を持つ\n",[34,1675,1676,1679,1682],{},[37,1677,1678],{},"ユーザー名（仮名）",[37,1680,1681],{},"プロフィール文",[37,1683,1684],{},"アバター写真",[342,1686,1687],{"id":1687},"ブログ機能",[34,1689,1690,1693,1710,1713,1716,1719,1722],{},[37,1691,1692],{},"ブログを作成することができ、公開することができる。",[37,1694,1695,1696],{},"ブログは以下の項目を持つ\n",[34,1697,1698,1701,1704,1707],{},[37,1699,1700],{},"タイトル",[37,1702,1703],{},"内容（リッチテキスト）",[37,1705,1706],{},"タグ（任意）",[37,1708,1709],{},"公開日時",[37,1711,1712],{},"下書きとして保存することができる。更新時も下書き可能。",[37,1714,1715],{},"任意数のタグを添付できる。タグはユーザーが新しく作成できる。",[37,1717,1718],{},"削除可能",[37,1720,1721],{},"画像の添付が可能。",[37,1723,1724],{},"アップロードした画像は管理できる。",[342,1726,1727],{"id":1727},"一覧検索機能",[34,1729,1730,1733,1736],{},[37,1731,1732],{},"サイトトップは投稿された記事が新着順に並ぶ。",[37,1734,1735],{},"任意のタグを選んで一覧表示することが可能。",[37,1737,1738],{},"部分一致検索も一応可能とする。",[47,1740,1741],{"id":1741},"アプリケーションアーキテクチャー",[20,1743,1744],{},"バックエンドにはLaravel9、管理画面はVue.js(2系)を使用して構築します。なおVue.jsはSPA構成とします。記事の一覧や詳細画面などの公開側はLaravelのレンダリングで表示します。",[87,1746],{":src":1747,":center":1446},"'vue_laravel_app\u002Farchitecture.jpg'",[20,1749,1750],{},"デザインに関してはセンスが皆無なので管理画面・公開側ともにBootstrapを使用します。",[27,1752,1753],{"id":1753},"環境構築",[20,1755,1756,1757,1762],{},"ではまずは環境構築を行っていきましょう。環境構築にはDockerを使用してLAMP環境を作ります。",[201,1758,1761],{"href":1759,"rel":1760},"https:\u002F\u002Fwww.twilio.com\u002Fblog\u002Fget-started-docker-laravel-jp",[205],"こちらの記事","を参考にし、",[34,1764,1765,1768,1771,1774],{},[37,1766,1767],{},"mysql5.7",[37,1769,1770],{},"phpmyadmin",[37,1772,1773],{},"apache2.4",[37,1775,1776],{},"php8.1",[20,1778,1779,1780,1783],{},"以上の構成を作成したいと思います。ディレクトリを作成して",[138,1781,1782],{},"docker-compose.yaml","を作成します。",[165,1785,1789],{"className":1786,"code":1787,"language":1788,"meta":173,"style":173},"language-bash shiki shiki-themes material-theme-ocean","mkdir ~\u002Flaravel_vue\ncd  ~\u002Flaravel_vue\n\ntouch docker-compose.yaml\nmkdir html\nmkdir web_1\ncd web_1\ntouch Dockerfile\n","bash",[138,1790,1791,1800,1808,1812,1820,1827,1834,1840],{"__ignoreMap":173},[184,1792,1793,1797],{"class":186,"line":187},[184,1794,1796],{"class":1795},"s5Dmg","mkdir",[184,1798,1799],{"class":236}," ~\u002Flaravel_vue\n",[184,1801,1802,1805],{"class":186,"line":193},[184,1803,1804],{"class":1253},"cd",[184,1806,1807],{"class":236},"  ~\u002Flaravel_vue\n",[184,1809,1810],{"class":186,"line":251},[184,1811,367],{"emptyLinePlaceholder":366},[184,1813,1814,1817],{"class":186,"line":259},[184,1815,1816],{"class":1795},"touch",[184,1818,1819],{"class":236}," docker-compose.yaml\n",[184,1821,1822,1824],{"class":186,"line":271},[184,1823,1796],{"class":1795},[184,1825,1826],{"class":236}," html\n",[184,1828,1829,1831],{"class":186,"line":279},[184,1830,1796],{"class":1795},[184,1832,1833],{"class":236}," web_1\n",[184,1835,1836,1838],{"class":186,"line":4},[184,1837,1804],{"class":1253},[184,1839,1833],{"class":236},[184,1841,1842,1844],{"class":186,"line":305},[184,1843,1816],{"class":1795},[184,1845,1846],{"class":236}," Dockerfile\n",[20,1848,1849,1852,1853,1856,1857,1859],{},[138,1850,1851],{},"html\u002F","という名前で作成したディレクトリにLaravelのソースが作成され、Dockerコンテナにマウントされる様にします。",[138,1854,1855],{},"web_1\u002F","ディレクトリにはphpとapacheが構築できる",[138,1858,158],{},"を作成してビルドします。",[20,1861,1862,1864],{},[138,1863,1782],{},"でDBとapache+phpと連結したいと思います。",[47,1866,1868],{"id":1867},"dockerfile設定","Dockerfile設定",[20,1870,1871],{},"まずはapacheとphpのイメージをDockerfileを作成します。",[165,1873,1878],{"className":1874,"code":1875,"filename":1876,"language":1877,"meta":173,"style":173},"language-dockerfie shiki shiki-themes material-theme-ocean","FROM php:8.1-apache\nRUN apt update \\\n        && apt install -y g++ libicu-dev libpq-dev libzip-dev zip zlib1g-dev \\\n        && mv \u002Fetc\u002Fapache2\u002Fmods-available\u002Frewrite.load \u002Fetc\u002Fapache2\u002Fmods-enabled\nRUN docker-php-ext-install pdo pdo_mysql\nWORKDIR \u002Fvar\u002Fwww\u002Fhtml\nRUN curl -sS https:\u002F\u002Fgetcomposer.org\u002Finstaller | php -- --install-dir=\u002Fusr\u002Flocal\u002Fbin --filename=composer\nRUN curl -fsSL https:\u002F\u002Fdeb.nodesource.com\u002Fsetup_lts.x | bash - \\\n    && apt-get install -y nodejs\n","web_1\u002FDockerfile","dockerfie",[138,1879,1880,1885,1890,1895,1900,1905,1910,1915,1920],{"__ignoreMap":173},[184,1881,1882],{"class":186,"line":187},[184,1883,1884],{},"FROM php:8.1-apache\n",[184,1886,1887],{"class":186,"line":193},[184,1888,1889],{},"RUN apt update \\\n",[184,1891,1892],{"class":186,"line":251},[184,1893,1894],{},"        && apt install -y g++ libicu-dev libpq-dev libzip-dev zip zlib1g-dev \\\n",[184,1896,1897],{"class":186,"line":259},[184,1898,1899],{},"        && mv \u002Fetc\u002Fapache2\u002Fmods-available\u002Frewrite.load \u002Fetc\u002Fapache2\u002Fmods-enabled\n",[184,1901,1902],{"class":186,"line":271},[184,1903,1904],{},"RUN docker-php-ext-install pdo pdo_mysql\n",[184,1906,1907],{"class":186,"line":279},[184,1908,1909],{},"WORKDIR \u002Fvar\u002Fwww\u002Fhtml\n",[184,1911,1912],{"class":186,"line":4},[184,1913,1914],{},"RUN curl -sS https:\u002F\u002Fgetcomposer.org\u002Finstaller | php -- --install-dir=\u002Fusr\u002Flocal\u002Fbin --filename=composer\n",[184,1916,1917],{"class":186,"line":305},[184,1918,1919],{},"RUN curl -fsSL https:\u002F\u002Fdeb.nodesource.com\u002Fsetup_lts.x | bash - \\\n",[184,1921,1922],{"class":186,"line":313},[184,1923,1924],{},"    && apt-get install -y nodejs\n",[20,1926,1927],{},"このファイルではphpとapacheが入った環境にてLaravelの依存PHPモジュールのインストールとcomposerをインストールする内容を記述しています。そしてLaravelのアセットビルドにはnode.jsを使用するのでそのインストールも記述しています。",[47,1929,1930],{"id":1930},"apacheの設定ファイルを作成",[20,1932,1933],{},"Laravelのドキュメントルートを設定するためにapacheの設定ファイル作成して、マウントできる様にします。",[165,1935,1937],{"className":1786,"code":1936,"language":1788,"meta":173,"style":173},"cd web_1\ntouch default.conf\n",[138,1938,1939,1945],{"__ignoreMap":173},[184,1940,1941,1943],{"class":186,"line":187},[184,1942,1804],{"class":1253},[184,1944,1833],{"class":236},[184,1946,1947,1949],{"class":186,"line":193},[184,1948,1816],{"class":1795},[184,1950,1951],{"class":236}," default.conf\n",[165,1953,1956],{"className":351,"code":1954,"filename":1955,"language":354,"meta":173,"style":173},"\u003CVirtualHost *:80>\n    ServerName laravel_docker\n    DocumentRoot \u002Fvar\u002Fwww\u002Fhtml\u002Fpublic\n\n    \u003CDirectory \"\u002Fvar\u002Fwww\u002Fhtml\u002Fpublic\">\n        AllowOverride All\n    \u003C\u002FDirectory>\n    ErrorLog ${APACHE_LOG_DIR}\u002Ferror.log\n    CustomLog ${APACHE_LOG_DIR}\u002Faccess.log combined\n\u003C\u002FVirtualHost>\n","default.conf",[138,1957,1958,1963,1968,1973,1977,1982,1987,1992,1997,2002],{"__ignoreMap":173},[184,1959,1960],{"class":186,"line":187},[184,1961,1962],{},"\u003CVirtualHost *:80>\n",[184,1964,1965],{"class":186,"line":193},[184,1966,1967],{},"    ServerName laravel_docker\n",[184,1969,1970],{"class":186,"line":251},[184,1971,1972],{},"    DocumentRoot \u002Fvar\u002Fwww\u002Fhtml\u002Fpublic\n",[184,1974,1975],{"class":186,"line":259},[184,1976,367],{"emptyLinePlaceholder":366},[184,1978,1979],{"class":186,"line":271},[184,1980,1981],{},"    \u003CDirectory \"\u002Fvar\u002Fwww\u002Fhtml\u002Fpublic\">\n",[184,1983,1984],{"class":186,"line":279},[184,1985,1986],{},"        AllowOverride All\n",[184,1988,1989],{"class":186,"line":4},[184,1990,1991],{},"    \u003C\u002FDirectory>\n",[184,1993,1994],{"class":186,"line":305},[184,1995,1996],{},"    ErrorLog ${APACHE_LOG_DIR}\u002Ferror.log\n",[184,1998,1999],{"class":186,"line":313},[184,2000,2001],{},"    CustomLog ${APACHE_LOG_DIR}\u002Faccess.log combined\n",[184,2003,2004],{"class":186,"line":321},[184,2005,2006],{},"\u003C\u002FVirtualHost>\n",[20,2008,2009,2010,2013],{},"このファイルはコンテナの",[138,2011,2012],{},"\u002Fetc\u002Fapache2\u002Fsites-enabled\u002F000-default.conf","にマウントされます。",[47,2015,2017],{"id":2016},"docker-composeyamlを作成","docker-compose.yamlを作成",[20,2019,2020],{},"次にdocker-comose.yamlを作成してDBとphpmyadmin",[165,2022,2025],{"className":215,"code":2023,"filename":2024,"language":218,"meta":173,"style":173},"version: '3'\nservices: \n  web_1:\n    build: .\u002Fweb_1\n    depends_on: \n      - db\n    volumes: \n      - .\u002Fhtml\u002F:\u002Fvar\u002Fwww\u002Fhtml\u002F\n      - .\u002Fweb_1\u002Fdefault.conf:\u002Fetc\u002Fapache2\u002Fsites-enabled\u002F000-default.conf\n    ports: \n      - \"8005:80\"\n  phpmyadmin:\n    image: phpmyadmin\n    restart: always\n    ports:\n      - \"8080:80\"\n    environment:\n     - PMA_ARBITRARY=1\n     - PMA_HOST=db:3306\n     - PMA_USER=root\n     - PMA_PASSWORD=rootroot\n  db:\n    image: mysql:5.7\n    command: mysqld --character-set-server=utf8 --collation-server=utf8_unicode_ci\n    environment:\n      MYSQL_DATABASE: preform\n      MYSQL_USER: test\n      MYSQL_PASSWORD: testtest\n      MYSQL_ROOT_PASSWORD: rootroot\n      TZ: Asia\u002FTokyo\n    ports: \n      - \"3306:3306\"\n    volumes: \n      - vue_laravel:\u002Fvar\u002Flib\u002Fmysql\nvolumes: \n  vue_laravel: {}\n","docker-comose.yaml",[138,2026,2027,2040,2049,2056,2065,2074,2081,2089,2096,2103,2111,2122,2129,2139,2149,2155,2166,2173,2181,2188,2195,2202,2209,2218,2228,2234,2244,2254,2264,2274,2284,2292,2303,2311,2318,2327],{"__ignoreMap":173},[184,2028,2029,2031,2033,2035,2038],{"class":186,"line":187},[184,2030,226],{"class":225},[184,2032,230],{"class":229},[184,2034,233],{"class":229},[184,2036,2037],{"class":236},"3",[184,2039,240],{"class":229},[184,2041,2042,2044,2046],{"class":186,"line":193},[184,2043,245],{"class":225},[184,2045,230],{"class":229},[184,2047,2048],{"class":1056}," \n",[184,2050,2051,2054],{"class":186,"line":251},[184,2052,2053],{"class":225},"  web_1",[184,2055,248],{"class":229},[184,2057,2058,2060,2062],{"class":186,"line":259},[184,2059,262],{"class":225},[184,2061,230],{"class":229},[184,2063,2064],{"class":236}," .\u002Fweb_1\n",[184,2066,2067,2070,2072],{"class":186,"line":271},[184,2068,2069],{"class":225},"    depends_on",[184,2071,230],{"class":229},[184,2073,2048],{"class":1056},[184,2075,2076,2078],{"class":186,"line":279},[184,2077,282],{"class":229},[184,2079,2080],{"class":236}," db\n",[184,2082,2083,2085,2087],{"class":186,"line":4},[184,2084,308],{"class":225},[184,2086,230],{"class":229},[184,2088,2048],{"class":1056},[184,2090,2091,2093],{"class":186,"line":305},[184,2092,282],{"class":229},[184,2094,2095],{"class":236}," .\u002Fhtml\u002F:\u002Fvar\u002Fwww\u002Fhtml\u002F\n",[184,2097,2098,2100],{"class":186,"line":313},[184,2099,282],{"class":229},[184,2101,2102],{"class":236}," .\u002Fweb_1\u002Fdefault.conf:\u002Fetc\u002Fapache2\u002Fsites-enabled\u002F000-default.conf\n",[184,2104,2105,2107,2109],{"class":186,"line":321},[184,2106,274],{"class":225},[184,2108,230],{"class":229},[184,2110,2048],{"class":1056},[184,2112,2113,2115,2117,2120],{"class":186,"line":329},[184,2114,282],{"class":229},[184,2116,285],{"class":229},[184,2118,2119],{"class":236},"8005:80",[184,2121,291],{"class":229},[184,2123,2124,2127],{"class":186,"line":412},[184,2125,2126],{"class":225},"  phpmyadmin",[184,2128,248],{"class":229},[184,2130,2131,2134,2136],{"class":186,"line":417},[184,2132,2133],{"class":225},"    image",[184,2135,230],{"class":229},[184,2137,2138],{"class":236}," phpmyadmin\n",[184,2140,2141,2144,2146],{"class":186,"line":423},[184,2142,2143],{"class":225},"    restart",[184,2145,230],{"class":229},[184,2147,2148],{"class":236}," always\n",[184,2150,2151,2153],{"class":186,"line":429},[184,2152,274],{"class":225},[184,2154,248],{"class":229},[184,2156,2157,2159,2161,2164],{"class":186,"line":435},[184,2158,282],{"class":229},[184,2160,285],{"class":229},[184,2162,2163],{"class":236},"8080:80",[184,2165,291],{"class":229},[184,2167,2168,2171],{"class":186,"line":441},[184,2169,2170],{"class":225},"    environment",[184,2172,248],{"class":229},[184,2174,2175,2178],{"class":186,"line":447},[184,2176,2177],{"class":229},"     -",[184,2179,2180],{"class":236}," PMA_ARBITRARY=1\n",[184,2182,2183,2185],{"class":186,"line":453},[184,2184,2177],{"class":229},[184,2186,2187],{"class":236}," PMA_HOST=db:3306\n",[184,2189,2190,2192],{"class":186,"line":459},[184,2191,2177],{"class":229},[184,2193,2194],{"class":236}," PMA_USER=root\n",[184,2196,2197,2199],{"class":186,"line":464},[184,2198,2177],{"class":229},[184,2200,2201],{"class":236}," PMA_PASSWORD=rootroot\n",[184,2203,2204,2207],{"class":186,"line":470},[184,2205,2206],{"class":225},"  db",[184,2208,248],{"class":229},[184,2210,2211,2213,2215],{"class":186,"line":476},[184,2212,2133],{"class":225},[184,2214,230],{"class":229},[184,2216,2217],{"class":236}," mysql:5.7\n",[184,2219,2220,2223,2225],{"class":186,"line":481},[184,2221,2222],{"class":225},"    command",[184,2224,230],{"class":229},[184,2226,2227],{"class":236}," mysqld --character-set-server=utf8 --collation-server=utf8_unicode_ci\n",[184,2229,2230,2232],{"class":186,"line":487},[184,2231,2170],{"class":225},[184,2233,248],{"class":229},[184,2235,2236,2239,2241],{"class":186,"line":493},[184,2237,2238],{"class":225},"      MYSQL_DATABASE",[184,2240,230],{"class":229},[184,2242,2243],{"class":236}," preform\n",[184,2245,2246,2249,2251],{"class":186,"line":499},[184,2247,2248],{"class":225},"      MYSQL_USER",[184,2250,230],{"class":229},[184,2252,2253],{"class":236}," test\n",[184,2255,2256,2259,2261],{"class":186,"line":505},[184,2257,2258],{"class":225},"      MYSQL_PASSWORD",[184,2260,230],{"class":229},[184,2262,2263],{"class":236}," testtest\n",[184,2265,2266,2269,2271],{"class":186,"line":511},[184,2267,2268],{"class":225},"      MYSQL_ROOT_PASSWORD",[184,2270,230],{"class":229},[184,2272,2273],{"class":236}," rootroot\n",[184,2275,2276,2279,2281],{"class":186,"line":517},[184,2277,2278],{"class":225},"      TZ",[184,2280,230],{"class":229},[184,2282,2283],{"class":236}," Asia\u002FTokyo\n",[184,2285,2286,2288,2290],{"class":186,"line":523},[184,2287,274],{"class":225},[184,2289,230],{"class":229},[184,2291,2048],{"class":1056},[184,2293,2294,2296,2298,2301],{"class":186,"line":528},[184,2295,282],{"class":229},[184,2297,285],{"class":229},[184,2299,2300],{"class":236},"3306:3306",[184,2302,291],{"class":229},[184,2304,2305,2307,2309],{"class":186,"line":533},[184,2306,308],{"class":225},[184,2308,230],{"class":229},[184,2310,2048],{"class":1056},[184,2312,2313,2315],{"class":186,"line":538},[184,2314,282],{"class":229},[184,2316,2317],{"class":236}," vue_laravel:\u002Fvar\u002Flib\u002Fmysql\n",[184,2319,2320,2323,2325],{"class":186,"line":543},[184,2321,2322],{"class":225},"volumes",[184,2324,230],{"class":229},[184,2326,2048],{"class":1056},[184,2328,2329,2332,2334],{"class":186,"line":549},[184,2330,2331],{"class":225},"  vue_laravel",[184,2333,230],{"class":229},[184,2335,2336],{"class":229}," {}\n",[20,2338,2339,2342,2343,2345,2346,2349,2350,2353],{},[138,2340,2341],{},"web_1","サービスでapacheとphpの環境をビルドします。そして",[138,2344,1851],{},"をコンテナ内の",[138,2347,2348],{},"\u002Fvar\u002Fwww\u002Fhtml\u002F","にマウントされます。ubuntuのapacheは",[138,2351,2352],{},"\u002Fetc\u002Fapache2\u002Fsites-enabled\u002F","配下にconfファイルを設置することで設定を追加したりオーバーライドできます。",[20,2355,2356,2358,2359,2362],{},[138,2357,1770],{},"サービスでphpmyadminを起動してDBの内容を編集しやすくします。",[138,2360,2361],{},"db","サービスではmysqlの環境を作成します。ボリュームの設定をして永続化を行います。以上の設定を行ったらコンテナを立ち上げます。",[165,2364,2366],{"className":1786,"code":2365,"language":1788,"meta":173,"style":173},"docker-compose up -d\n\nCreating laravel_vue_phpmyadmin_1 ... done\nCreating laravel_vue_db_1         ... done\nCreating laravel_vue_web_1_1      ... done\n",[138,2367,2368,2379,2383,2397,2409],{"__ignoreMap":173},[184,2369,2370,2373,2376],{"class":186,"line":187},[184,2371,2372],{"class":1795},"docker-compose",[184,2374,2375],{"class":236}," up",[184,2377,2378],{"class":236}," -d\n",[184,2380,2381],{"class":186,"line":193},[184,2382,367],{"emptyLinePlaceholder":366},[184,2384,2385,2388,2391,2394],{"class":186,"line":251},[184,2386,2387],{"class":1795},"Creating",[184,2389,2390],{"class":236}," laravel_vue_phpmyadmin_1",[184,2392,2393],{"class":236}," ...",[184,2395,2396],{"class":236}," done\n",[184,2398,2399,2401,2404,2407],{"class":186,"line":259},[184,2400,2387],{"class":1795},[184,2402,2403],{"class":236}," laravel_vue_db_1",[184,2405,2406],{"class":236},"         ...",[184,2408,2396],{"class":236},[184,2410,2411,2413,2416,2419],{"class":186,"line":271},[184,2412,2387],{"class":1795},[184,2414,2415],{"class":236}," laravel_vue_web_1_1",[184,2417,2418],{"class":236},"      ...",[184,2420,2396],{"class":236},[20,2422,2423],{},"そしてコンテナ内に入ってLaravelをインストールしていきます。",[165,2425,2428],{"className":2426,"code":2427,"language":170},[168],"docker exec -it laravel_vue_web_1_1 \u002Fbin\u002Fbash\n\nroot@a790844c74d6:\u002Fvar\u002Fwww\u002Fhtml# composer \nroot@a790844c74d6:\u002Fvar\u002Fwww\u002Fhtml# composer create-project laravel\u002Flaravel .\u002F\n",[138,2429,2427],{"__ignoreMap":173},[20,2431,2432,2433,2436,2437,2440,2441,2443],{},"これで",[138,2434,2435],{},"\u002Fvar\u002Fwww\u002Fhtml","配下にLaravelプロジェクトが入ります。コンテナの",[138,2438,2439],{}," \u002Fvar\u002Fwww\u002Fhtml","はホストマシンの",[138,2442,1851],{},"に同期されます。",[20,2445,2446,2448,2449,2452],{},[138,2447,1851],{},"配下にソースが生成されたのを確認しましたら、",[138,2450,2451],{},".env","ファイルにDBの接続設定を記述します。",[165,2454,2457],{"className":2455,"code":2456,"filename":2451,"language":170,"meta":173},[168],"DB_CONNECTION=mysql\nDB_HOST=db\nDB_PORT=3306\nDB_DATABASE=laravel\nDB_USERNAME=root\nDB_PASSWORD=rootroot\n",[138,2458,2456],{"__ignoreMap":173},[20,2460,2461,2462,2465,2466,2469,2470,2473],{},"phpmyadminとかで",[138,2463,2464],{},"laravel","データベースは作成しておいてください。",[138,2467,2468],{},"DB_HOST","はdockerのDBのサービス名を入力します。設定したらブラウザで",[138,2471,2472],{},"localhost:8005","にアクセスします。以下の様なウェルカムページが表示されれば成功です。",[87,2475],{":src":2476,":center":1446},"'vue_laravel_app\u002Flaravel_welcome.png'",[20,2478,2479],{},"（特に断りがない限り、以降ではDockerコンテナ内の操作とします。）",[27,2481,2483],{"id":2482},"laravelとvueセットアップ","LaravelとVueセットアップ",[20,2485,2486,2487,2490],{},"それではアセットと認証機能のセットアップをしましょう。今回は",[138,2488,2489],{},"laravel\u002Fbreeze","をスターターキットとして利用します。",[165,2492,2495],{"className":2493,"code":2494,"language":170},[168],"composer require laravel\u002Fbreeze --dev\n\nphp artisan breeze:install\nBreeze scaffolding installed successfully.\nPlease execute the \"npm install && npm run dev\" command to build your assets.\n\nnpm install\nnpm run dev\nphp artisan migrate\nMigration table created successfully.\nMigrating: 2014_10_12_000000_create_users_table\nMigrated:  2014_10_12_000000_create_users_table (23.69ms)\nMigrating: 2014_10_12_100000_create_password_resets_table\nMigrated:  2014_10_12_100000_create_password_resets_table (10.45ms)\nMigrating: 2019_08_19_000000_create_failed_jobs_table\nMigrated:  2019_08_19_000000_create_failed_jobs_table (10.60ms)\nMigrating: 2019_12_14_000001_create_personal_access_tokens_table\nMigrated:  2019_12_14_000001_create_personal_access_tokens_table (14.54ms)\n",[138,2496,2494],{"__ignoreMap":173},[20,2498,2499],{},"マイグレーションが完了したので登録などができる様になっているはずです。ひとまずLaravelのセットアップは以上となります。",[27,2501,2503],{"id":2502},"とりあえずspa構成ができるかチェック","とりあえずSPA構成ができるかチェック",[20,2505,2506],{},"次は管理画面のフロントエンドのセットアップをしていきます。管理画面はvue.jsで作成し、公開側コンテンツはLaravelのサーバーサイドレンダリングをします。ここではSPA構成のセットアップをします。",[47,2508,2510],{"id":2509},"spaのビューとルートの設定","SPAのビューとルートの設定",[20,2512,2513,2516],{},[138,2514,2515],{},"\u002Fsystem","配下を管理画面として構築していきます。以下の様なビューテンプレートとルートを作成します。",[165,2518,2523],{"className":2519,"code":2520,"filename":2521,"language":2522,"meta":173,"style":173},"language-php shiki shiki-themes material-theme-ocean","Route::get('\u002Fsystem\u002F{path?}', function () {\n    return view('admin');\n})->where('path', '.*')->name('admin');\n","routes\u002Fweb.php","php",[138,2524,2525,2530,2535],{"__ignoreMap":173},[184,2526,2527],{"class":186,"line":187},[184,2528,2529],{},"Route::get('\u002Fsystem\u002F{path?}', function () {\n",[184,2531,2532],{"class":186,"line":193},[184,2533,2534],{},"    return view('admin');\n",[184,2536,2537],{"class":186,"line":251},[184,2538,2539],{},"})->where('path', '.*')->name('admin');\n",[20,2541,2542,2543,2545],{},"このルートは",[138,2544,2515],{},"配下すべてのルートに対してadminビューを返すことを示しています。",[165,2547,2550],{"className":2519,"code":2548,"filename":2549,"language":2522,"meta":173,"style":173},"\u003C!DOCTYPE html>\n\u003Chtml lang=\"{{ str_replace('_', '-', app()->getLocale()) }}\">\n    \u003Chead>\n        \u003Cmeta charset=\"utf-8\">\n        \u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n        \u003Cmeta name=\"csrf-token\" content=\"{{ csrf_token() }}\">\n\n        \u003Ctitle>{{ config('app.name', 'Laravel') }}\u003C\u002Ftitle>\n\n        \u003C!-- Fonts -->\n        \u003Clink rel=\"stylesheet\" href=\"https:\u002F\u002Ffonts.googleapis.com\u002Fcss2?family=Nunito:wght@400;600;700&display=swap\">\n\n        \u003C!-- Styles -->\n        \u003Clink rel=\"stylesheet\" href=\"{{ asset('css\u002Fapp.css') }}\">\n\n    \u003C\u002Fhead>\n    \u003Cbody>\n        \u003Cdiv class=\"font-sans text-gray-900 antialiased\">\n            \u003Cdiv id=\"app\">\n                \u003Crouter-view\u002F>\n            \u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n    \u003C\u002Fbody>\n    \u003C!-- Scripts -->\n    \u003Cscript src=\"{{ asset('js\u002Fadmin\u002Findex.js') }}\" defer>\u003C\u002Fscript>\n\u003C\u002Fhtml>\n","resources\u002Fview\u002Fadmin.blade.php",[138,2551,2552,2557,2562,2567,2572,2577,2582,2586,2591,2595,2600,2605,2609,2614,2619,2623,2628,2633,2638,2643,2648,2653,2658,2663,2668,2673],{"__ignoreMap":173},[184,2553,2554],{"class":186,"line":187},[184,2555,2556],{},"\u003C!DOCTYPE html>\n",[184,2558,2559],{"class":186,"line":193},[184,2560,2561],{},"\u003Chtml lang=\"{{ str_replace('_', '-', app()->getLocale()) }}\">\n",[184,2563,2564],{"class":186,"line":251},[184,2565,2566],{},"    \u003Chead>\n",[184,2568,2569],{"class":186,"line":259},[184,2570,2571],{},"        \u003Cmeta charset=\"utf-8\">\n",[184,2573,2574],{"class":186,"line":271},[184,2575,2576],{},"        \u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n",[184,2578,2579],{"class":186,"line":279},[184,2580,2581],{},"        \u003Cmeta name=\"csrf-token\" content=\"{{ csrf_token() }}\">\n",[184,2583,2584],{"class":186,"line":4},[184,2585,367],{"emptyLinePlaceholder":366},[184,2587,2588],{"class":186,"line":305},[184,2589,2590],{},"        \u003Ctitle>{{ config('app.name', 'Laravel') }}\u003C\u002Ftitle>\n",[184,2592,2593],{"class":186,"line":313},[184,2594,367],{"emptyLinePlaceholder":366},[184,2596,2597],{"class":186,"line":321},[184,2598,2599],{},"        \u003C!-- Fonts -->\n",[184,2601,2602],{"class":186,"line":329},[184,2603,2604],{},"        \u003Clink rel=\"stylesheet\" href=\"https:\u002F\u002Ffonts.googleapis.com\u002Fcss2?family=Nunito:wght@400;600;700&display=swap\">\n",[184,2606,2607],{"class":186,"line":412},[184,2608,367],{"emptyLinePlaceholder":366},[184,2610,2611],{"class":186,"line":417},[184,2612,2613],{},"        \u003C!-- Styles -->\n",[184,2615,2616],{"class":186,"line":423},[184,2617,2618],{},"        \u003Clink rel=\"stylesheet\" href=\"{{ asset('css\u002Fapp.css') }}\">\n",[184,2620,2621],{"class":186,"line":429},[184,2622,367],{"emptyLinePlaceholder":366},[184,2624,2625],{"class":186,"line":435},[184,2626,2627],{},"    \u003C\u002Fhead>\n",[184,2629,2630],{"class":186,"line":441},[184,2631,2632],{},"    \u003Cbody>\n",[184,2634,2635],{"class":186,"line":447},[184,2636,2637],{},"        \u003Cdiv class=\"font-sans text-gray-900 antialiased\">\n",[184,2639,2640],{"class":186,"line":453},[184,2641,2642],{},"            \u003Cdiv id=\"app\">\n",[184,2644,2645],{"class":186,"line":459},[184,2646,2647],{},"                \u003Crouter-view\u002F>\n",[184,2649,2650],{"class":186,"line":464},[184,2651,2652],{},"            \u003C\u002Fdiv>\n",[184,2654,2655],{"class":186,"line":470},[184,2656,2657],{},"        \u003C\u002Fdiv>\n",[184,2659,2660],{"class":186,"line":476},[184,2661,2662],{},"    \u003C\u002Fbody>\n",[184,2664,2665],{"class":186,"line":481},[184,2666,2667],{},"    \u003C!-- Scripts -->\n",[184,2669,2670],{"class":186,"line":487},[184,2671,2672],{},"    \u003Cscript src=\"{{ asset('js\u002Fadmin\u002Findex.js') }}\" defer>\u003C\u002Fscript>\n",[184,2674,2675],{"class":186,"line":493},[184,2676,2677],{},"\u003C\u002Fhtml>\n",[20,2679,2680,2681,2684,2685,2688],{},"テンプレートは上記の様にして",[138,2682,2683],{},"\u003Cdiv id=\"app\">\u003Crouter-view\u002F>\u003C\u002Fdiv>","を設置して、vueのエントリーポイントと、vueのビルドファイルを読み込む様にします。実際はヘッダーなどを分けたほうがいいですが、今はとりあえずこんな感じでOKです。\n次に",[138,2686,2687],{},"'js\u002Fadmin\u002Findex.js","で読み込ませるjsファイルを作成していきます。",[47,2690,2691],{"id":2691},"必要なライブラリをインストール",[20,2693,2694,2695,2698,2699,159,2702,2705,2706,2708,2709,159,2712,2715],{},"一昔前の",[138,2696,2697],{},"laravel\u002Fui","の時は",[138,2700,2701],{},"vue",[138,2703,2704],{},"bootstrap","の設定がインストール時に自動設定されますが、今回の",[138,2707,2489],{},"は",[138,2710,2711],{},"alpinejs",[138,2713,2714],{},"tailwindcss","が入っているので、vue一式を自分でインストールする必要があります。まずはVue・Vuex・Vue-Routerをインストールしましょう。",[13,2717,2720],{"className":2718},[16,2719],"alert-danger","\nこの記事を作成した2021年3月現在、Vue3がリリースされておりバージョンを指定しないと、それぞれ最新版がインストールされる可能性があります。この記事はVue2を用いて解説しますのでバージョンを指定してインストールしています。\n",[165,2722,2725],{"className":2723,"code":2724,"language":170},[168],"npm install vue@2 vue-loader@15 vue-template-compiler@2 --save-dev\nnpm install vuex@3 vue-router@3\n",[138,2726,2724],{"__ignoreMap":173},[47,2728,2730],{"id":2729},"spa用のjsファイルを作成する","SPA用のJSファイルを作成する",[20,2732,2733,2734,2737],{},"それでは",[138,2735,2736],{},"resources\u002Fjs","配下にSPAのファイルを作成を行います。以下の様にファイルとディレクトリを作成してください。",[165,2739,2742],{"className":2740,"code":2741,"language":170},[168],"js\n├── admin\n│   ├── index.js\n│   ├── router.js\n│   └── store.js\n└── vue\n    └── page\n        ├── Home.vue\n        └── Profile.vue\n",[138,2743,2741],{"__ignoreMap":173},[20,2745,2746],{},"store.jsとrouter.jsは以下の様にします。",[165,2748,2753],{"className":2749,"code":2750,"filename":2751,"language":2752,"meta":173,"style":173},"language-js shiki shiki-themes material-theme-ocean","export default {\n    state:{},\n    getters: {},\n    mutations: {},\n    actions: {}\n}\n","store.js","js",[138,2754,2755,2766,2774,2784,2793,2802],{"__ignoreMap":173},[184,2756,2757,2761,2764],{"class":186,"line":187},[184,2758,2760],{"class":2759},"s6cf3","export",[184,2762,2763],{"class":2759}," default",[184,2765,1270],{"class":229},[184,2767,2768,2771],{"class":186,"line":193},[184,2769,2770],{"class":225},"    state",[184,2772,2773],{"class":229},":{},\n",[184,2775,2776,2779,2781],{"class":186,"line":251},[184,2777,2778],{"class":225},"    getters",[184,2780,230],{"class":229},[184,2782,2783],{"class":229}," {},\n",[184,2785,2786,2789,2791],{"class":186,"line":259},[184,2787,2788],{"class":225},"    mutations",[184,2790,230],{"class":229},[184,2792,2783],{"class":229},[184,2794,2795,2798,2800],{"class":186,"line":271},[184,2796,2797],{"class":225},"    actions",[184,2799,230],{"class":229},[184,2801,2336],{"class":229},[184,2803,2804],{"class":186,"line":279},[184,2805,400],{"class":229},[20,2807,2808],{},"storeはひとまず必要な型だけ準備します。",[165,2810,2813],{"className":2749,"code":2811,"filename":2812,"language":2752,"meta":173,"style":173},"import Home from '~js\u002Fvue\u002Fpage\u002FHome.vue';\nimport Profile from '~js\u002Fvue\u002Fpage\u002FProfile.vue';\n\nconst routes = [\n    { path: '\u002Fsystem', component: Home },\n    { path: '\u002Fsystem\u002Fprofile', component: Profile },\n]\n\nexport default routes;\n","router.js",[138,2814,2815,2835,2853,2857,2870,2898,2923,2928,2932],{"__ignoreMap":173},[184,2816,2817,2820,2823,2826,2828,2831,2833],{"class":186,"line":187},[184,2818,2819],{"class":2759},"import",[184,2821,2822],{"class":1056}," Home ",[184,2824,2825],{"class":2759},"from",[184,2827,233],{"class":229},[184,2829,2830],{"class":236},"~js\u002Fvue\u002Fpage\u002FHome.vue",[184,2832,1260],{"class":229},[184,2834,1321],{"class":229},[184,2836,2837,2839,2842,2844,2846,2849,2851],{"class":186,"line":193},[184,2838,2819],{"class":2759},[184,2840,2841],{"class":1056}," Profile ",[184,2843,2825],{"class":2759},[184,2845,233],{"class":229},[184,2847,2848],{"class":236},"~js\u002Fvue\u002Fpage\u002FProfile.vue",[184,2850,1260],{"class":229},[184,2852,1321],{"class":229},[184,2854,2855],{"class":186,"line":251},[184,2856,367],{"emptyLinePlaceholder":366},[184,2858,2859,2862,2865,2867],{"class":186,"line":259},[184,2860,2861],{"class":993},"const",[184,2863,2864],{"class":1056}," routes ",[184,2866,1033],{"class":229},[184,2868,2869],{"class":1056}," [\n",[184,2871,2872,2875,2878,2880,2882,2884,2886,2888,2891,2893,2895],{"class":186,"line":271},[184,2873,2874],{"class":229},"    {",[184,2876,2877],{"class":225}," path",[184,2879,230],{"class":229},[184,2881,233],{"class":229},[184,2883,2515],{"class":236},[184,2885,1260],{"class":229},[184,2887,1267],{"class":229},[184,2889,2890],{"class":225}," component",[184,2892,230],{"class":229},[184,2894,2822],{"class":1056},[184,2896,2897],{"class":229},"},\n",[184,2899,2900,2902,2904,2906,2908,2911,2913,2915,2917,2919,2921],{"class":186,"line":279},[184,2901,2874],{"class":229},[184,2903,2877],{"class":225},[184,2905,230],{"class":229},[184,2907,233],{"class":229},[184,2909,2910],{"class":236},"\u002Fsystem\u002Fprofile",[184,2912,1260],{"class":229},[184,2914,1267],{"class":229},[184,2916,2890],{"class":225},[184,2918,230],{"class":229},[184,2920,2841],{"class":1056},[184,2922,2897],{"class":229},[184,2924,2925],{"class":186,"line":4},[184,2926,2927],{"class":1056},"]\n",[184,2929,2930],{"class":186,"line":305},[184,2931,367],{"emptyLinePlaceholder":366},[184,2933,2934,2936,2938,2941],{"class":186,"line":313},[184,2935,2760],{"class":2759},[184,2937,2763],{"class":2759},[184,2939,2940],{"class":1056}," routes",[184,2942,1321],{"class":229},[20,2944,2945,2946,2949],{},"router.jsでは指定のパスに対してどのコンポーネントを出すかを定義します。",[138,2947,2948],{},"~js","という記述は後で解説しますので、ひとまず記述してください。",[20,2951,2952],{},"router.jsで参照しているコンポーネントは以下の様にしておきます。",[165,2954,2958],{"className":2955,"code":2956,"filename":2957,"language":2701,"meta":173,"style":173},"language-vue shiki shiki-themes material-theme-ocean","\u003Ctemplate>\n    \u003Cdiv>\n        home\n        \u003Crouter-link to=\"\u002Fsystem\u002Fprofile\">profile\u003C\u002Frouter-link>\n    \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\u003Cscript>\nexport default {\n    name:'home'\n}\n\u003C\u002Fscript>\n","Home.vue",[138,2959,2960,2969,2978,2983,3013,3022,3030,3038,3046,3060,3064],{"__ignoreMap":173},[184,2961,2962,2964,2967],{"class":186,"line":187},[184,2963,1002],{"class":229},[184,2965,2966],{"class":225},"template",[184,2968,997],{"class":229},[184,2970,2971,2974,2976],{"class":186,"line":193},[184,2972,2973],{"class":229},"    \u003C",[184,2975,13],{"class":225},[184,2977,997],{"class":229},[184,2979,2980],{"class":186,"line":251},[184,2981,2982],{"class":1056},"        home\n",[184,2984,2985,2988,2991,2994,2996,2998,3000,3002,3004,3007,3009,3011],{"class":186,"line":259},[184,2986,2987],{"class":229},"        \u003C",[184,2989,2990],{"class":225},"router-link",[184,2992,2993],{"class":993}," to",[184,2995,1033],{"class":229},[184,2997,1036],{"class":229},[184,2999,2910],{"class":236},[184,3001,1036],{"class":229},[184,3003,1053],{"class":229},[184,3005,3006],{"class":1056},"profile",[184,3008,1060],{"class":229},[184,3010,2990],{"class":225},[184,3012,997],{"class":229},[184,3014,3015,3018,3020],{"class":186,"line":271},[184,3016,3017],{"class":229},"    \u003C\u002F",[184,3019,13],{"class":225},[184,3021,997],{"class":229},[184,3023,3024,3026,3028],{"class":186,"line":279},[184,3025,1060],{"class":229},[184,3027,2966],{"class":225},[184,3029,997],{"class":229},[184,3031,3032,3034,3036],{"class":186,"line":4},[184,3033,1002],{"class":229},[184,3035,1137],{"class":225},[184,3037,997],{"class":229},[184,3039,3040,3042,3044],{"class":186,"line":305},[184,3041,2760],{"class":2759},[184,3043,2763],{"class":2759},[184,3045,1270],{"class":229},[184,3047,3048,3051,3053,3055,3058],{"class":186,"line":313},[184,3049,3050],{"class":225},"    name",[184,3052,230],{"class":229},[184,3054,1260],{"class":229},[184,3056,3057],{"class":236},"home",[184,3059,240],{"class":229},[184,3061,3062],{"class":186,"line":321},[184,3063,400],{"class":229},[184,3065,3066,3068,3070],{"class":186,"line":329},[184,3067,1060],{"class":229},[184,3069,1137],{"class":225},[184,3071,997],{"class":229},[165,3073,3076],{"className":2955,"code":3074,"filename":3075,"language":2701,"meta":173,"style":173},"\u003Ctemplate>\n    \u003Cdiv>\n        profile\n    \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\u003Cscript>\nexport default {\n    name:'profile'\n}\n\u003C\u002Fscript>\n","Profile.vue",[138,3077,3078,3086,3094,3099,3107,3115,3123,3131,3143,3147],{"__ignoreMap":173},[184,3079,3080,3082,3084],{"class":186,"line":187},[184,3081,1002],{"class":229},[184,3083,2966],{"class":225},[184,3085,997],{"class":229},[184,3087,3088,3090,3092],{"class":186,"line":193},[184,3089,2973],{"class":229},[184,3091,13],{"class":225},[184,3093,997],{"class":229},[184,3095,3096],{"class":186,"line":251},[184,3097,3098],{"class":1056},"        profile\n",[184,3100,3101,3103,3105],{"class":186,"line":259},[184,3102,3017],{"class":229},[184,3104,13],{"class":225},[184,3106,997],{"class":229},[184,3108,3109,3111,3113],{"class":186,"line":271},[184,3110,1060],{"class":229},[184,3112,2966],{"class":225},[184,3114,997],{"class":229},[184,3116,3117,3119,3121],{"class":186,"line":279},[184,3118,1002],{"class":229},[184,3120,1137],{"class":225},[184,3122,997],{"class":229},[184,3124,3125,3127,3129],{"class":186,"line":4},[184,3126,2760],{"class":2759},[184,3128,2763],{"class":2759},[184,3130,1270],{"class":229},[184,3132,3133,3135,3137,3139,3141],{"class":186,"line":305},[184,3134,3050],{"class":225},[184,3136,230],{"class":229},[184,3138,1260],{"class":229},[184,3140,3006],{"class":236},[184,3142,240],{"class":229},[184,3144,3145],{"class":186,"line":313},[184,3146,400],{"class":229},[184,3148,3149,3151,3153],{"class":186,"line":321},[184,3150,1060],{"class":229},[184,3152,1137],{"class":225},[184,3154,997],{"class":229},[20,3156,3157],{},"index.jsは以下の様に記述します。",[165,3159,3162],{"className":2749,"code":3160,"filename":3161,"language":2752,"meta":173,"style":173},"window.axios = require('axios');\nwindow.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';\n\nimport Vue from 'vue';\n\nimport Vuex from 'vuex';\nimport index from '.\u002Fstore';\nVue.use(Vuex);\nconst store = new Vuex.Store(index);\n\nimport VueRouter from 'vue-router'\nimport routes from '.\u002Frouter';\nVue.use(VueRouter)\nconst router =  new VueRouter({mode: 'history',routes})\n\nconst app = new Vue({\n    store,\n    router,\n}).$mount(\"#app\");\n","index.js",[138,3163,3164,3192,3236,3240,3257,3261,3279,3297,3312,3336,3340,3356,3373,3384,3427,3431,3450,3458,3465],{"__ignoreMap":173},[184,3165,3166,3169,3171,3174,3176,3179,3181,3183,3186,3188,3190],{"class":186,"line":187},[184,3167,3168],{"class":1056},"window",[184,3170,1304],{"class":229},[184,3172,3173],{"class":1056},"axios ",[184,3175,1033],{"class":229},[184,3177,3178],{"class":1253}," require",[184,3180,1257],{"class":1056},[184,3182,1260],{"class":229},[184,3184,3185],{"class":236},"axios",[184,3187,1260],{"class":229},[184,3189,1294],{"class":1056},[184,3191,1321],{"class":229},[184,3193,3194,3196,3198,3200,3202,3205,3207,3210,3212,3215,3217,3220,3222,3225,3227,3229,3232,3234],{"class":186,"line":193},[184,3195,3168],{"class":1056},[184,3197,1304],{"class":229},[184,3199,3185],{"class":1056},[184,3201,1304],{"class":229},[184,3203,3204],{"class":1056},"defaults",[184,3206,1304],{"class":229},[184,3208,3209],{"class":1056},"headers",[184,3211,1304],{"class":229},[184,3213,3214],{"class":1056},"common[",[184,3216,1260],{"class":229},[184,3218,3219],{"class":236},"X-Requested-With",[184,3221,1260],{"class":229},[184,3223,3224],{"class":1056},"] ",[184,3226,1033],{"class":229},[184,3228,233],{"class":229},[184,3230,3231],{"class":236},"XMLHttpRequest",[184,3233,1260],{"class":229},[184,3235,1321],{"class":229},[184,3237,3238],{"class":186,"line":251},[184,3239,367],{"emptyLinePlaceholder":366},[184,3241,3242,3244,3247,3249,3251,3253,3255],{"class":186,"line":259},[184,3243,2819],{"class":2759},[184,3245,3246],{"class":1056}," Vue ",[184,3248,2825],{"class":2759},[184,3250,233],{"class":229},[184,3252,2701],{"class":236},[184,3254,1260],{"class":229},[184,3256,1321],{"class":229},[184,3258,3259],{"class":186,"line":271},[184,3260,367],{"emptyLinePlaceholder":366},[184,3262,3263,3265,3268,3270,3272,3275,3277],{"class":186,"line":279},[184,3264,2819],{"class":2759},[184,3266,3267],{"class":1056}," Vuex ",[184,3269,2825],{"class":2759},[184,3271,233],{"class":229},[184,3273,3274],{"class":236},"vuex",[184,3276,1260],{"class":229},[184,3278,1321],{"class":229},[184,3280,3281,3283,3286,3288,3290,3293,3295],{"class":186,"line":4},[184,3282,2819],{"class":2759},[184,3284,3285],{"class":1056}," index ",[184,3287,2825],{"class":2759},[184,3289,233],{"class":229},[184,3291,3292],{"class":236},".\u002Fstore",[184,3294,1260],{"class":229},[184,3296,1321],{"class":229},[184,3298,3299,3302,3304,3307,3310],{"class":186,"line":305},[184,3300,3301],{"class":1056},"Vue",[184,3303,1304],{"class":229},[184,3305,3306],{"class":1253},"use",[184,3308,3309],{"class":1056},"(Vuex)",[184,3311,1321],{"class":229},[184,3313,3314,3316,3319,3321,3323,3326,3328,3331,3334],{"class":186,"line":313},[184,3315,2861],{"class":993},[184,3317,3318],{"class":1056}," store ",[184,3320,1033],{"class":229},[184,3322,1250],{"class":229},[184,3324,3325],{"class":1056}," Vuex",[184,3327,1304],{"class":229},[184,3329,3330],{"class":1253},"Store",[184,3332,3333],{"class":1056},"(index)",[184,3335,1321],{"class":229},[184,3337,3338],{"class":186,"line":321},[184,3339,367],{"emptyLinePlaceholder":366},[184,3341,3342,3344,3347,3349,3351,3354],{"class":186,"line":329},[184,3343,2819],{"class":2759},[184,3345,3346],{"class":1056}," VueRouter ",[184,3348,2825],{"class":2759},[184,3350,233],{"class":229},[184,3352,3353],{"class":236},"vue-router",[184,3355,240],{"class":229},[184,3357,3358,3360,3362,3364,3366,3369,3371],{"class":186,"line":412},[184,3359,2819],{"class":2759},[184,3361,2864],{"class":1056},[184,3363,2825],{"class":2759},[184,3365,233],{"class":229},[184,3367,3368],{"class":236},".\u002Frouter",[184,3370,1260],{"class":229},[184,3372,1321],{"class":229},[184,3374,3375,3377,3379,3381],{"class":186,"line":417},[184,3376,3301],{"class":1056},[184,3378,1304],{"class":229},[184,3380,3306],{"class":1253},[184,3382,3383],{"class":1056},"(VueRouter)\n",[184,3385,3386,3388,3391,3393,3396,3399,3401,3404,3407,3409,3411,3414,3416,3418,3421,3424],{"class":186,"line":423},[184,3387,2861],{"class":993},[184,3389,3390],{"class":1056}," router ",[184,3392,1033],{"class":229},[184,3394,3395],{"class":229},"  new",[184,3397,3398],{"class":1253}," VueRouter",[184,3400,1257],{"class":1056},[184,3402,3403],{"class":229},"{",[184,3405,3406],{"class":225},"mode",[184,3408,230],{"class":229},[184,3410,233],{"class":229},[184,3412,3413],{"class":236},"history",[184,3415,1260],{"class":229},[184,3417,1267],{"class":229},[184,3419,3420],{"class":1056},"routes",[184,3422,3423],{"class":229},"}",[184,3425,3426],{"class":1056},")\n",[184,3428,3429],{"class":186,"line":429},[184,3430,367],{"emptyLinePlaceholder":366},[184,3432,3433,3435,3438,3440,3442,3445,3447],{"class":186,"line":435},[184,3434,2861],{"class":993},[184,3436,3437],{"class":1056}," app ",[184,3439,1033],{"class":229},[184,3441,1250],{"class":229},[184,3443,3444],{"class":1253}," Vue",[184,3446,1257],{"class":1056},[184,3448,3449],{"class":229},"{\n",[184,3451,3452,3455],{"class":186,"line":441},[184,3453,3454],{"class":1056},"    store",[184,3456,3457],{"class":229},",\n",[184,3459,3460,3463],{"class":186,"line":447},[184,3461,3462],{"class":1056},"    router",[184,3464,3457],{"class":229},[184,3466,3467,3469,3471,3473,3476,3478,3480,3483,3485,3487],{"class":186,"line":453},[184,3468,3423],{"class":229},[184,3470,1294],{"class":1056},[184,3472,1304],{"class":229},[184,3474,3475],{"class":1253},"$mount",[184,3477,1257],{"class":1056},[184,3479,1036],{"class":229},[184,3481,3482],{"class":236},"#app",[184,3484,1036],{"class":229},[184,3486,1294],{"class":1056},[184,3488,1321],{"class":229},[20,3490,3491,3492,3495],{},"axiosはLaravelインストール時に入っているのでそのまま利用しています。このファイルではVuexとVue-routerの連携を行い、最終的に",[138,3493,3494],{},"\u003Cdiv id=\"app\">\u003C\u002Fdiv>","にレンダリングされる様にします。",[20,3497,3498,3499,3502],{},"上記のファイルを作成した後、",[138,3500,3501],{},"webpac.mix.js","を修正します。",[47,3504,3505],{"id":3505},"mixファイルの作成",[20,3507,3508,3509,3512,3513,3516,3517,3520,3521,3524],{},"このようなVueのプロジェクトwebpackを使用しますがLaravelは簡単な記述で",[138,3510,3511],{},"resources","配下を簡単に",[138,3514,3515],{},"public\u002F","配下に出力してくれます。またvueやsassのコンパイル設定を",[138,3518,3519],{},"webpack.config.js","の様に書く必要がありません。どのファイルをコンパイル対象にするかなどは",[138,3522,3523],{},"webpack.mix.js","に記載します。以下の様に修正します。",[165,3526,3528],{"className":2749,"code":3527,"filename":3519,"language":2752,"meta":173,"style":173},"const mix = require('laravel-mix');\nconst path = require('path');\n\nmix.webpackConfig({\n    resolve: {\n      alias: {\n        '~js': path.resolve('.\u002Fresources\u002Fjs'),\n      }\n    }\n});\nmix.js('resources\u002Fjs\u002Fadmin\u002Findex.js', 'public\u002Fjs\u002Fadmin').vue();\n",[138,3529,3530,3554,3578,3582,3596,3605,3614,3645,3650,3654,3662],{"__ignoreMap":173},[184,3531,3532,3534,3537,3539,3541,3543,3545,3548,3550,3552],{"class":186,"line":187},[184,3533,2861],{"class":993},[184,3535,3536],{"class":1056}," mix ",[184,3538,1033],{"class":229},[184,3540,3178],{"class":1253},[184,3542,1257],{"class":1056},[184,3544,1260],{"class":229},[184,3546,3547],{"class":236},"laravel-mix",[184,3549,1260],{"class":229},[184,3551,1294],{"class":1056},[184,3553,1321],{"class":229},[184,3555,3556,3558,3561,3563,3565,3567,3569,3572,3574,3576],{"class":186,"line":193},[184,3557,2861],{"class":993},[184,3559,3560],{"class":1056}," path ",[184,3562,1033],{"class":229},[184,3564,3178],{"class":1253},[184,3566,1257],{"class":1056},[184,3568,1260],{"class":229},[184,3570,3571],{"class":236},"path",[184,3573,1260],{"class":229},[184,3575,1294],{"class":1056},[184,3577,1321],{"class":229},[184,3579,3580],{"class":186,"line":251},[184,3581,367],{"emptyLinePlaceholder":366},[184,3583,3584,3587,3589,3592,3594],{"class":186,"line":259},[184,3585,3586],{"class":1056},"mix",[184,3588,1304],{"class":229},[184,3590,3591],{"class":1253},"webpackConfig",[184,3593,1257],{"class":1056},[184,3595,3449],{"class":229},[184,3597,3598,3601,3603],{"class":186,"line":271},[184,3599,3600],{"class":225},"    resolve",[184,3602,230],{"class":229},[184,3604,1270],{"class":229},[184,3606,3607,3610,3612],{"class":186,"line":279},[184,3608,3609],{"class":225},"      alias",[184,3611,230],{"class":229},[184,3613,1270],{"class":229},[184,3615,3616,3619,3621,3623,3625,3627,3629,3632,3634,3636,3639,3641,3643],{"class":186,"line":4},[184,3617,3618],{"class":229},"        '",[184,3620,2948],{"class":225},[184,3622,1260],{"class":229},[184,3624,230],{"class":229},[184,3626,2877],{"class":1056},[184,3628,1304],{"class":229},[184,3630,3631],{"class":1253},"resolve",[184,3633,1257],{"class":1056},[184,3635,1260],{"class":229},[184,3637,3638],{"class":236},".\u002Fresources\u002Fjs",[184,3640,1260],{"class":229},[184,3642,1294],{"class":1056},[184,3644,3457],{"class":229},[184,3646,3647],{"class":186,"line":305},[184,3648,3649],{"class":229},"      }\n",[184,3651,3652],{"class":186,"line":313},[184,3653,520],{"class":229},[184,3655,3656,3658,3660],{"class":186,"line":321},[184,3657,3423],{"class":229},[184,3659,1294],{"class":1056},[184,3661,1321],{"class":229},[184,3663,3664,3666,3668,3670,3672,3674,3677,3679,3681,3683,3686,3688,3690,3692,3694,3697],{"class":186,"line":329},[184,3665,3586],{"class":1056},[184,3667,1304],{"class":229},[184,3669,2752],{"class":1253},[184,3671,1257],{"class":1056},[184,3673,1260],{"class":229},[184,3675,3676],{"class":236},"resources\u002Fjs\u002Fadmin\u002Findex.js",[184,3678,1260],{"class":229},[184,3680,1267],{"class":229},[184,3682,233],{"class":229},[184,3684,3685],{"class":236},"public\u002Fjs\u002Fadmin",[184,3687,1260],{"class":229},[184,3689,1294],{"class":1056},[184,3691,1304],{"class":229},[184,3693,2701],{"class":1253},[184,3695,3696],{"class":1056},"()",[184,3698,1321],{"class":229},[20,3700,3701,3704,3705,3707],{},[138,3702,3703],{},"mix.webpackConfig","はのように",[138,3706,3519],{},"に書く様な他の細かいwebpackの設定を定義でき、ここではエイリアスを定義しています。",[20,3709,3710,3713,3714,3716,3717,3719],{},[138,3711,3712],{},"'~js': path.resolve('.\u002Fresources\u002Fjs')","という記述は",[138,3715,2948],{},"というパスの記載があった場合は「",[138,3718,2736],{},"までの絶対ルート」であるとwebpackに支持しています。この様にエイリアスを定義することで相対パスによるファイルの参照がなくなります。router.jsで以下の様に定義していましたが、",[165,3721,3723],{"className":2749,"code":3722,"filename":2812,"language":2752,"meta":173,"style":173},"import Home from '~js\u002Fvue\u002Fpage\u002FHome.vue';\nimport Profile from '~js\u002Fvue\u002Fpage\u002FProfile.vue';\n",[138,3724,3725,3741],{"__ignoreMap":173},[184,3726,3727,3729,3731,3733,3735,3737,3739],{"class":186,"line":187},[184,3728,2819],{"class":2759},[184,3730,2822],{"class":1056},[184,3732,2825],{"class":2759},[184,3734,233],{"class":229},[184,3736,2830],{"class":236},[184,3738,1260],{"class":229},[184,3740,1321],{"class":229},[184,3742,3743,3745,3747,3749,3751,3753,3755],{"class":186,"line":193},[184,3744,2819],{"class":2759},[184,3746,2841],{"class":1056},[184,3748,2825],{"class":2759},[184,3750,233],{"class":229},[184,3752,2848],{"class":236},[184,3754,1260],{"class":229},[184,3756,1321],{"class":229},[20,3758,3759],{},"もしエイリアスが使えない場合は",[165,3761,3763],{"className":2749,"code":3762,"filename":2812,"language":2752,"meta":173,"style":173},"import Home from '..\u002Fvue\u002Fpage\u002FHome.vue';\nimport Profile from '..\u002Fvue\u002Fpage\u002FProfile.vue';\n",[138,3764,3765,3782],{"__ignoreMap":173},[184,3766,3767,3769,3771,3773,3775,3778,3780],{"class":186,"line":187},[184,3768,2819],{"class":2759},[184,3770,2822],{"class":1056},[184,3772,2825],{"class":2759},[184,3774,233],{"class":229},[184,3776,3777],{"class":236},"..\u002Fvue\u002Fpage\u002FHome.vue",[184,3779,1260],{"class":229},[184,3781,1321],{"class":229},[184,3783,3784,3786,3788,3790,3792,3795,3797],{"class":186,"line":193},[184,3785,2819],{"class":2759},[184,3787,2841],{"class":1056},[184,3789,2825],{"class":2759},[184,3791,233],{"class":229},[184,3793,3794],{"class":236},"..\u002Fvue\u002Fpage\u002FProfile.vue",[184,3796,1260],{"class":229},[184,3798,1321],{"class":229},[20,3800,3801,3802,3804],{},"このように相対パスになってしまい、もし構成を変えようとした時に相対関係の修正が必要となります。その対策としてエイリアスを定めておくと今後の管理がしやすいです。",[138,3803,3712],{},"はjsディレクトリを参照する様にしています。",[20,3806,3807,3810,3811,3813,3814,3817],{},[138,3808,3809],{},"mix.js('resources\u002Fjs\u002Fadmin\u002Findex.js', 'public\u002Fjs\u002Fadmin').vue();"," にて",[138,3812,3161],{},"をソースとして",[138,3815,3816],{},"'public\u002Fjs\u002Fadmin'","配下に出力し、vueコンパイルを行う様に定義してます。",[20,3819,3820,3821,3824,3825,3828],{},"問題なければ",[138,3822,3823],{},"npm run prod","をプロジェクト ルートでコマンドを叩いてコンパイルを行います。完了後に",[138,3826,3827],{},"public\u002Fjs\u002Fadmin\u002Findex.js","というものが出力されていることを確認してください。",[47,3830,3831],{"id":3831},"反映の確認",[20,3833,3834,3835,3838,3839,3841,3842,3844],{},"jsファイルが出力されましたら",[138,3836,3837],{},"http:\u002F\u002Flocalhost:8005\u002Fsystem","にアクセスします。以下の様に表示され、",[138,3840,3006],{},"というリンクをクリックした際にURLが変わり、",[138,3843,3075],{},"に書かれた内容が表示されればSPAはできています。",[87,3846],{":src":3847,":center":1446},"'vue_laravel_app\u002Fhomevue.png'",[20,3849,3850,3852,3853,3856],{},[138,3851,3006],{},"というリンクをクリック後 or ",[138,3854,3855],{},"http:\u002F\u002Flocalhost:8005\u002Fsystem\u002Fprofile","にアクセスしますと以下の様になります。",[87,3858],{":src":3859,":center":1446},"'vue_laravel_app\u002Fprofilevue.png'",[20,3861,3862],{},"これでSPAが設定できたことを確認できました。",[27,3864,3866],{"id":3865},"次回は","次回は..",[20,3868,3869],{},"今回は環境構築とLaravel、Vue SPAのセットアップまでとなります。来週はweb apiを通じたSPAでの認証と簡単なプロフィールの更新機能を作成してみます。",[1530,3871,3872],{},"html pre.shiki code .s5Dmg, html code.shiki .s5Dmg{--shiki-default:#FFCB6B}html pre.shiki code .sfyAc, html code.shiki .sfyAc{--shiki-default:#C3E88D}html pre.shiki code .sdLwU, html code.shiki .sdLwU{--shiki-default:#82AAFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s-wAU, html code.shiki .s-wAU{--shiki-default:#F07178}html pre.shiki code .sAklC, html code.shiki .sAklC{--shiki-default:#89DDFF}html pre.shiki code .s0W1g, html code.shiki .s0W1g{--shiki-default:#BABED8}html pre.shiki code .s6cf3, html code.shiki .s6cf3{--shiki-default:#89DDFF;--shiki-default-font-style:italic}html pre.shiki code .sJ14y, html code.shiki .sJ14y{--shiki-default:#C792EA}",{"title":173,"searchDepth":251,"depth":251,"links":3874},[3875,3883,3888,3889,3896],{"id":1641,"depth":193,"text":1641,"children":3876},[3877,3882],{"id":1647,"depth":251,"text":1647,"children":3878},[3879,3880,3881],{"id":1653,"depth":259,"text":1653},{"id":1687,"depth":259,"text":1687},{"id":1727,"depth":259,"text":1727},{"id":1741,"depth":251,"text":1741},{"id":1753,"depth":193,"text":1753,"children":3884},[3885,3886,3887],{"id":1867,"depth":251,"text":1868},{"id":1930,"depth":251,"text":1930},{"id":2016,"depth":251,"text":2017},{"id":2482,"depth":193,"text":2483},{"id":2502,"depth":193,"text":2503,"children":3890},[3891,3892,3893,3894,3895],{"id":2509,"depth":251,"text":2510},{"id":2691,"depth":251,"text":2691},{"id":2729,"depth":251,"text":2730},{"id":3505,"depth":251,"text":3505},{"id":3831,"depth":251,"text":3831},{"id":3865,"depth":193,"text":3866},[1561],"2022-03-02","アプリの解説とシリーズの概要",{},"\u002Fseries\u002Fvue-laravel-app-1",{"title":1579,"description":3899},"vue_laravel_app","Vue SPA x Laravelでつくる実務パチモンアプリ","series\u002Fvue-laravel-app-1",[2464,2522,2752,2701],"vue_laravel_app\u002Fseries.png","QS_7_9yqTbYmh1Gt9XAVtEnkXBXd1wzlct80a3xdm_U",{"id":3910,"title":3911,"body":3912,"category":5311,"createdAt":5312,"description":3899,"extension":1564,"index":187,"meta":5313,"navigation":366,"path":5314,"publish":366,"seo":5315,"series":5316,"seriesTitle":5317,"stem":5318,"tag":5319,"thumbnail":5321,"updatedAt":1575,"__hash__":5322},"series\u002Fseries\u002Flaravel-to-django-1.md","Laravel使いがDjangoでwebアプリを作るよその１：アプリの概要と環境構築",{"type":10,"value":3913,"toc":5291},[3914,3917,3920,3923,3934,3937,3940,3943,3963,3966,3969,3988,3990,3996,4037,4044,4047,4050,4090,4093,4095,4100,4371,4376,4385,4398,4405,4422,4445,4450,4456,4471,4474,4477,4480,4482,4504,4506,4535,4537,4545,4548,4551,4555,4558,4561,4572,4632,4635,4691,4704,4707,4710,4785,4801,4804,4817,5081,5084,5102,5105,5112,5118,5125,5131,5141,5148,5159,5178,5184,5193,5196,5246,5253,5282,5285,5288],[20,3915,3916],{},"こんにちはjunです。最近Laravelでシステムを作る機会が何回かあったので、Laravelによる構築がかなり得意になってきました。しかし会社の方で「pythonを使用した機械学習や統計の機能をwebアプリとして使用できるアプリを開発したい」という企画がでてきました。",[20,3918,3919],{},"pythonで機械学習や統計の処理（モデル）を作成し、UIやロジックの部分をwebフレームワークで作成します。できたらwebフレームワークの処理部分にて統計ロジックをimportして、データの引き渡しを行いたいと思いました。しかしLaravelはPHPなので連携が難しいです。",[20,3921,3922],{},"そのため「Python製のwebフレームワークであるdjangoを利用できないか？」と思い、Djangoの勉強をスタートしました。チュートリアルはひとまず終えましたが、Laravelをやっていたおかげで",[34,3924,3925,3928,3931],{},[37,3926,3927],{},"どんな機能があると便利か？",[37,3929,3930],{},"Laravelのこの機能がDjangoのこの記述にあたるのか？",[37,3932,3933],{},"実務上必要となるこの機能を実装するには何を使えばいいのか？",[20,3935,3936],{},"という視点を用いて学習することができました。このシリーズでは「Laravelを使っていたPHP開発者がDjangoとPythonを使用してアプリを作る」ことを通じてDjangoの学習をしたいと思っています。記事内ではDjangoにおける実装とLaravelにおける実装をリンクしながら解説していきたいと思います。",[20,3938,3939],{},"利用するDjangoのバージョンは3.2で、Laravelは8とします。第１回のこの記事は環境構築とアプリの概要、Djangoの開発コンセプトやディレクトリ説明がメインとなります。",[20,3941,3942],{},"なおシリーズでは以下の内容を押さえていることを前提としています",[34,3944,3945,3948,3951,3954,3957,3960],{},[37,3946,3947],{},"Laravelの実務開発経験があるまたは、完成したアプリを作ったことがある。",[37,3949,3950],{},"Laravelのコンセプトやディレクトリ構成を知っている。",[37,3952,3953],{},"PythonがPCにインストールされ、基本的な記述を理解している。",[37,3955,3956],{},"PHPを触ったことが多いが、Pythonはそれほどない。",[37,3958,3959],{},"Djangoのチュートリアルは行っており、必須の内容は知っている。",[37,3961,3962],{},"MVCの概念を知っている、使える。",[20,3964,3965],{},"それでは早速始めていきましょう。",[20,3967,3968],{},"参考資料",[34,3970,3971,3978,3981],{},[37,3972,3973],{},[201,3974,3977],{"href":3975,"rel":3976},"https:\u002F\u002Fdocs.djangoproject.com\u002Fja\u002F3.2",[205],"Django ドキュメント3.2",[37,3979,3980],{},"書籍『現場で使える Django の教科書』",[37,3982,3983],{},[201,3984,3987],{"href":3985,"rel":3986},"https:\u002F\u002Fdocs.docker.jp\u002Fcompose\u002Fdjango.html",[205],"クィックスタート: Compose と Django - Docker ドキュメント",[27,3989,1753],{"id":1753},[20,3991,3992,3993,3995],{},"今回の環境構築はdockerを用いて作成しようと思います。適当なディレクトリ を作成し、",[138,3994,1782],{},"とビルドファイルを作成します。",[165,3997,3999],{"className":1786,"code":3998,"language":1788,"meta":173,"style":173},"mkdir laravel_django\ncd laravel_django\n\ntouch Dockerfile\ntouch docker-compose.yaml\nmkdir source\n",[138,4000,4001,4008,4014,4018,4024,4030],{"__ignoreMap":173},[184,4002,4003,4005],{"class":186,"line":187},[184,4004,1796],{"class":1795},[184,4006,4007],{"class":236}," laravel_django\n",[184,4009,4010,4012],{"class":186,"line":193},[184,4011,1804],{"class":1253},[184,4013,4007],{"class":236},[184,4015,4016],{"class":186,"line":251},[184,4017,367],{"emptyLinePlaceholder":366},[184,4019,4020,4022],{"class":186,"line":259},[184,4021,1816],{"class":1795},[184,4023,1846],{"class":236},[184,4025,4026,4028],{"class":186,"line":271},[184,4027,1816],{"class":1795},[184,4029,1819],{"class":236},[184,4031,4032,4034],{"class":186,"line":279},[184,4033,1796],{"class":1795},[184,4035,4036],{"class":236}," source\n",[20,4038,4039,4040,4043],{},"まずはDockerfileでDjangoが稼働するpythonの環境を作成します。なお、この環境ではApacheやnginxといったwebサーバーとpythonとの連携設定は行わないものとします。コンテナ内で",[138,4041,4042],{},"run serve","を行います。",[47,4045,158],{"id":4046},"dockerfile",[20,4048,4049],{},"Dockerfileを以下の様に記述します。",[165,4051,4053],{"className":179,"code":4052,"language":158,"meta":173,"style":173},"FROM python:3\nENV PYTHONUNBUFFERED 1\nRUN mkdir \u002Fcode\nWORKDIR \u002Fcode\nADD requirements.txt \u002Fcode\u002F\nRUN pip install -r requirements.txt\nADD . \u002Fcode\u002F\n",[138,4054,4055,4060,4065,4070,4075,4080,4085],{"__ignoreMap":173},[184,4056,4057],{"class":186,"line":187},[184,4058,4059],{},"FROM python:3\n",[184,4061,4062],{"class":186,"line":193},[184,4063,4064],{},"ENV PYTHONUNBUFFERED 1\n",[184,4066,4067],{"class":186,"line":251},[184,4068,4069],{},"RUN mkdir \u002Fcode\n",[184,4071,4072],{"class":186,"line":259},[184,4073,4074],{},"WORKDIR \u002Fcode\n",[184,4076,4077],{"class":186,"line":271},[184,4078,4079],{},"ADD requirements.txt \u002Fcode\u002F\n",[184,4081,4082],{"class":186,"line":279},[184,4083,4084],{},"RUN pip install -r requirements.txt\n",[184,4086,4087],{"class":186,"line":4},[184,4088,4089],{},"ADD . \u002Fcode\u002F\n",[20,4091,4092],{},"python3 イメージをもとにpythonが動く環境を用意します。",[47,4094,2372],{"id":2372},[20,4096,4097,4099],{},[138,4098,1782],{},"は以下の様に記述します。",[165,4101,4103],{"className":215,"code":4102,"filename":1782,"language":218,"meta":173,"style":173},"version: '3'\n\nservices:\n  db:\n    image: mysql:5.7\n    command: mysqld --character-set-server=utf8 --collation-server=utf8_unicode_ci\n    environment:\n      MYSQL_DATABASE: preform\n      MYSQL_USER: test\n      MYSQL_PASSWORD: testtest\n      MYSQL_ROOT_PASSWORD: rootroot\n      TZ: Asia\u002FTokyo\n    ports: \n      - \"3306:3306\"\n    volumes: \n      - djangodemo:\u002Fvar\u002Flib\u002Fmysql\n  phpmyadmin:\n    image: phpmyadmin\n    ports:\n      - \"8080:80\"\n    environment:\n     - PMA_ARBITRARY=1\n     - PMA_HOST=db:3306\n     - PMA_USER=root\n     - PMA_PASSWORD=rootroot\n  web:\n    build: .\n    command: python3 manage.py runserver 0.0.0.0:8000\n    volumes:\n      - .\u002Fsource:\u002Fcode\n    ports:\n      - \"8000:8000\"\n    depends_on:\n      - db\nvolumes: \n  djangodemo: {}\n",[138,4104,4105,4117,4121,4127,4133,4141,4149,4155,4163,4171,4179,4187,4195,4203,4213,4221,4228,4234,4242,4248,4258,4264,4270,4276,4282,4288,4295,4303,4312,4318,4325,4331,4342,4348,4354,4362],{"__ignoreMap":173},[184,4106,4107,4109,4111,4113,4115],{"class":186,"line":187},[184,4108,226],{"class":225},[184,4110,230],{"class":229},[184,4112,233],{"class":229},[184,4114,2037],{"class":236},[184,4116,240],{"class":229},[184,4118,4119],{"class":186,"line":193},[184,4120,367],{"emptyLinePlaceholder":366},[184,4122,4123,4125],{"class":186,"line":251},[184,4124,245],{"class":225},[184,4126,248],{"class":229},[184,4128,4129,4131],{"class":186,"line":259},[184,4130,2206],{"class":225},[184,4132,248],{"class":229},[184,4134,4135,4137,4139],{"class":186,"line":271},[184,4136,2133],{"class":225},[184,4138,230],{"class":229},[184,4140,2217],{"class":236},[184,4142,4143,4145,4147],{"class":186,"line":279},[184,4144,2222],{"class":225},[184,4146,230],{"class":229},[184,4148,2227],{"class":236},[184,4150,4151,4153],{"class":186,"line":4},[184,4152,2170],{"class":225},[184,4154,248],{"class":229},[184,4156,4157,4159,4161],{"class":186,"line":305},[184,4158,2238],{"class":225},[184,4160,230],{"class":229},[184,4162,2243],{"class":236},[184,4164,4165,4167,4169],{"class":186,"line":313},[184,4166,2248],{"class":225},[184,4168,230],{"class":229},[184,4170,2253],{"class":236},[184,4172,4173,4175,4177],{"class":186,"line":321},[184,4174,2258],{"class":225},[184,4176,230],{"class":229},[184,4178,2263],{"class":236},[184,4180,4181,4183,4185],{"class":186,"line":329},[184,4182,2268],{"class":225},[184,4184,230],{"class":229},[184,4186,2273],{"class":236},[184,4188,4189,4191,4193],{"class":186,"line":412},[184,4190,2278],{"class":225},[184,4192,230],{"class":229},[184,4194,2283],{"class":236},[184,4196,4197,4199,4201],{"class":186,"line":417},[184,4198,274],{"class":225},[184,4200,230],{"class":229},[184,4202,2048],{"class":1056},[184,4204,4205,4207,4209,4211],{"class":186,"line":423},[184,4206,282],{"class":229},[184,4208,285],{"class":229},[184,4210,2300],{"class":236},[184,4212,291],{"class":229},[184,4214,4215,4217,4219],{"class":186,"line":429},[184,4216,308],{"class":225},[184,4218,230],{"class":229},[184,4220,2048],{"class":1056},[184,4222,4223,4225],{"class":186,"line":435},[184,4224,282],{"class":229},[184,4226,4227],{"class":236}," djangodemo:\u002Fvar\u002Flib\u002Fmysql\n",[184,4229,4230,4232],{"class":186,"line":441},[184,4231,2126],{"class":225},[184,4233,248],{"class":229},[184,4235,4236,4238,4240],{"class":186,"line":447},[184,4237,2133],{"class":225},[184,4239,230],{"class":229},[184,4241,2138],{"class":236},[184,4243,4244,4246],{"class":186,"line":453},[184,4245,274],{"class":225},[184,4247,248],{"class":229},[184,4249,4250,4252,4254,4256],{"class":186,"line":459},[184,4251,282],{"class":229},[184,4253,285],{"class":229},[184,4255,2163],{"class":236},[184,4257,291],{"class":229},[184,4259,4260,4262],{"class":186,"line":464},[184,4261,2170],{"class":225},[184,4263,248],{"class":229},[184,4265,4266,4268],{"class":186,"line":470},[184,4267,2177],{"class":229},[184,4269,2180],{"class":236},[184,4271,4272,4274],{"class":186,"line":476},[184,4273,2177],{"class":229},[184,4275,2187],{"class":236},[184,4277,4278,4280],{"class":186,"line":481},[184,4279,2177],{"class":229},[184,4281,2194],{"class":236},[184,4283,4284,4286],{"class":186,"line":487},[184,4285,2177],{"class":229},[184,4287,2201],{"class":236},[184,4289,4290,4293],{"class":186,"line":493},[184,4291,4292],{"class":225},"  web",[184,4294,248],{"class":229},[184,4296,4297,4299,4301],{"class":186,"line":499},[184,4298,262],{"class":225},[184,4300,230],{"class":229},[184,4302,268],{"class":267},[184,4304,4305,4307,4309],{"class":186,"line":505},[184,4306,2222],{"class":225},[184,4308,230],{"class":229},[184,4310,4311],{"class":236}," python3 manage.py runserver 0.0.0.0:8000\n",[184,4313,4314,4316],{"class":186,"line":511},[184,4315,308],{"class":225},[184,4317,248],{"class":229},[184,4319,4320,4322],{"class":186,"line":517},[184,4321,282],{"class":229},[184,4323,4324],{"class":236}," .\u002Fsource:\u002Fcode\n",[184,4326,4327,4329],{"class":186,"line":523},[184,4328,274],{"class":225},[184,4330,248],{"class":229},[184,4332,4333,4335,4337,4340],{"class":186,"line":528},[184,4334,282],{"class":229},[184,4336,285],{"class":229},[184,4338,4339],{"class":236},"8000:8000",[184,4341,291],{"class":229},[184,4343,4344,4346],{"class":186,"line":533},[184,4345,2069],{"class":225},[184,4347,248],{"class":229},[184,4349,4350,4352],{"class":186,"line":538},[184,4351,282],{"class":229},[184,4353,2080],{"class":236},[184,4355,4356,4358,4360],{"class":186,"line":543},[184,4357,2322],{"class":225},[184,4359,230],{"class":229},[184,4361,2048],{"class":1056},[184,4363,4364,4367,4369],{"class":186,"line":549},[184,4365,4366],{"class":225},"  djangodemo",[184,4368,230],{"class":229},[184,4370,2336],{"class":229},[20,4372,4373,4375],{},[138,4374,2361],{},"ではデータベースを定義しています。DjangoはSQLiteを使用した簡易的なDBが用意されています。ただし実務ではmysqlなどのDBサーバーを使用することが多いので、今回はmysqlを使用できる様にします。",[13,4377,4379,4380],{"className":4378},[16,17],"\nDjangoでmysqlを使用するためにはpythonモジュールのmysqlclientが必要です。この環境にはデフォルトでないので注意してください。他のDBサーバーを使用する場合は適宜ドライバをインストールしてください。\n",[201,4381,4384],{"target":4382,"href":4383},"_blank","https:\u002F\u002Fdocs.djangoproject.com\u002Fja\u002F3.2\u002Ftopics\u002Finstall\u002F#get-your-database-running","本家ドキュメントを参照",[20,4386,4387,4388,4390,4391,4393,4394,4397],{},"DB内部をすぐに確認できる様にphpmyadminを入れておきます。（好みなのでなくてもいいです。）\nそして",[138,4389,747],{},"では同階層にある",[138,4392,158],{},"をビルドして実行できる様になります。立ち上げ時に",[138,4395,4396],{},"python3 manage.py runserver 0.0.0.0:8000","をしてwebサーバを立ち上げる設定にしています。",[20,4399,4400,4401,4404],{},"このコンテナを起動する前に ",[138,4402,4403],{},"requirements.txt"," をDockerfileと同じ階層に生成しておき、以下の内容を記述します。",[165,4406,4410],{"className":4407,"code":4408,"filename":4403,"language":4409,"meta":173,"style":173},"language-txt shiki shiki-themes material-theme-ocean","Django==3.2\nmysqlclient==2.1.0\n","txt",[138,4411,4412,4417],{"__ignoreMap":173},[184,4413,4414],{"class":186,"line":187},[184,4415,4416],{},"Django==3.2\n",[184,4418,4419],{"class":186,"line":193},[184,4420,4421],{},"mysqlclient==2.1.0\n",[20,4423,4424,4426,4427,4430,4431,4434,4435,4437,4438,4440,4441,4444],{},[138,4425,4403],{}," はpythonライブラリ管理ツールpipの設定ファイルです。npmの",[138,4428,4429],{},"package.json","やcomposerの",[138,4432,4433],{},"composer.json","といったものと同じです。pythonのモジュール管理はpipを使用することが多く、",[138,4436,4403],{}," は環境ないの依存するモジュールとそのバージョンを記述して配置しておきます。そして",[138,4439,4403],{}," がある階層で ",[138,4442,4443],{},"pip install","をすることで自動的に依存関係をインストールしてくれます。Django3.2とmysqlclientを記述しておきます。",[20,4446,4447,4449],{},[138,4448,4403],{},"を記述したら以下のコマンドを実行してDjangoのソースを作成します。",[165,4451,4454],{"className":4452,"code":4453,"language":170},[168],"docker-compose run web django-admin.py startproject djangodemo .\n",[138,4455,4453],{"__ignoreMap":173},[20,4457,4458,4459,4462,4463,4466,4467,4470],{},"すると",[138,4460,4461],{},"source\u002F","配下にDjangoのソースが生成されるはずです。\nそして準備が整ったら",[138,4464,4465],{},"docker-compose up -d --build"," を行って構築を行います。問題なくいった場合はブラウザにて ",[138,4468,4469],{},"localhost:8000"," を閲覧すると、Djangoのウェルカムページが表示されるはずです。",[87,4472],{":src":4473,":width":90},"'laralve_to_django\u002Fwelcome.png'",[27,4475,4476],{"id":4476},"アプリ概要",[20,4478,4479],{},"今回作成するアプリはn○teみたいなブログサービスを作成しようと思います。",[342,4481,1653],{"id":1653},[34,4483,4484,4486,4488,4490,4492,4494],{},[37,4485,1658],{},[37,4487,1661],{},[37,4489,1664],{},[37,4491,1667],{},[37,4493,1670],{},[37,4495,1673,4496],{},[34,4497,4498,4500,4502],{},[37,4499,1678],{},[37,4501,1681],{},[37,4503,1684],{},[342,4505,1687],{"id":1687},[34,4507,4508,4510,4522,4524,4526,4528,4530,4532],{},[37,4509,1692],{},[37,4511,1695,4512],{},[34,4513,4514,4516,4518,4520],{},[37,4515,1700],{},[37,4517,1703],{},[37,4519,1706],{},[37,4521,1709],{},[37,4523,1712],{},[37,4525,1715],{},[37,4527,1718],{},[37,4529,1721],{},[37,4531,1724],{},[37,4533,4534],{},"公開側からコメント可能",[342,4536,1727],{"id":1727},[34,4538,4539,4541,4543],{},[37,4540,1732],{},[37,4542,1735],{},[37,4544,1738],{},[20,4546,4547],{},"一通りユーザーエンティティから、記事のCURD、リレーションの練習はできると思います。すべてのビューはpython側でレンダリングを行い、Vue.jsなどは今回使用しません。違う機会にrest apiを使用した構成を作ってみたいとおおいます。",[20,4549,4550],{},"デザインに関してはいつものbootstrapを使用します。",[27,4552,4554],{"id":4553},"djangoの大まかな解説","Djangoの大まかな解説",[20,4556,4557],{},"詳細なロジックなどは次の記事で解説したいと思います。今回はDjangoのディレクトリ構成やLaravelと似ているとこやリンクしている中核的な機能を解説したいと思います。",[47,4559,4560],{"id":4560},"コマンドによる制御",[20,4562,4563,4564,4567,4568,4571],{},"Laravelでは ",[138,4565,4566],{},"php artisan"," を使用することでコントローラーを作ったり、マイグレーションを実行したりできます。Djangoにも似た様なものがあります。使用する時はソーストップの ",[138,4569,4570],{},"manage.py"," がある箇所で実行します。例えば以下の様な機能があります。",[165,4573,4575],{"className":1786,"code":4574,"language":1788,"meta":173,"style":173},"# 簡易webサーバーを起動\npython3 manage.py runserve\n\n# マイグレーションを実行\npython3 manage.py migrate\n\n# アプリのテンプレートを作成\npython3 manage.py startapp APP_NAME\n\n",[138,4576,4577,4582,4593,4597,4602,4611,4615,4620],{"__ignoreMap":173},[184,4578,4579],{"class":186,"line":187},[184,4580,4581],{"class":1069},"# 簡易webサーバーを起動\n",[184,4583,4584,4587,4590],{"class":186,"line":193},[184,4585,4586],{"class":1795},"python3",[184,4588,4589],{"class":236}," manage.py",[184,4591,4592],{"class":236}," runserve\n",[184,4594,4595],{"class":186,"line":251},[184,4596,367],{"emptyLinePlaceholder":366},[184,4598,4599],{"class":186,"line":259},[184,4600,4601],{"class":1069},"# マイグレーションを実行\n",[184,4603,4604,4606,4608],{"class":186,"line":271},[184,4605,4586],{"class":1795},[184,4607,4589],{"class":236},[184,4609,4610],{"class":236}," migrate\n",[184,4612,4613],{"class":186,"line":279},[184,4614,367],{"emptyLinePlaceholder":366},[184,4616,4617],{"class":186,"line":4},[184,4618,4619],{"class":1069},"# アプリのテンプレートを作成\n",[184,4621,4622,4624,4626,4629],{"class":186,"line":305},[184,4623,4586],{"class":1795},[184,4625,4589],{"class":236},[184,4627,4628],{"class":236}," startapp",[184,4630,4631],{"class":236}," APP_NAME\n",[20,4633,4634],{},"Laravelで例えると",[165,4636,4638],{"className":1786,"code":4637,"language":1788,"meta":173,"style":173},"# 簡易webサーバーを起動\nphp artisan serve\n\n# マイグレーションを実行\nphp artisan migrate\n\n# コントローラーのテンプレートを作成\nphp artisan make:controller CONTROLLER_NAME\n\n",[138,4639,4640,4644,4654,4658,4662,4670,4674,4679],{"__ignoreMap":173},[184,4641,4642],{"class":186,"line":187},[184,4643,4581],{"class":1069},[184,4645,4646,4648,4651],{"class":186,"line":193},[184,4647,2522],{"class":1795},[184,4649,4650],{"class":236}," artisan",[184,4652,4653],{"class":236}," serve\n",[184,4655,4656],{"class":186,"line":251},[184,4657,367],{"emptyLinePlaceholder":366},[184,4659,4660],{"class":186,"line":259},[184,4661,4601],{"class":1069},[184,4663,4664,4666,4668],{"class":186,"line":271},[184,4665,2522],{"class":1795},[184,4667,4650],{"class":236},[184,4669,4610],{"class":236},[184,4671,4672],{"class":186,"line":279},[184,4673,367],{"emptyLinePlaceholder":366},[184,4675,4676],{"class":186,"line":4},[184,4677,4678],{"class":1069},"# コントローラーのテンプレートを作成\n",[184,4680,4681,4683,4685,4688],{"class":186,"line":305},[184,4682,2522],{"class":1795},[184,4684,4650],{"class":236},[184,4686,4687],{"class":236}," make:controller",[184,4689,4690],{"class":236}," CONTROLLER_NAME\n",[20,4692,4693,4694,4697,4698,4703],{},"以上の様になります。この ",[138,4695,4696],{},"python3 manage.py","で実行できる内容は",[201,4699,4702],{"href":4700,"rel":4701},"https:\u002F\u002Fdocs.djangoproject.com\u002Fja\u002F3.2\u002Fref\u002Fdjango-admin\u002F",[205],"こちらのドキュメント","で知ることができます。",[47,4705,4706],{"id":4706},"ディレクトリとファイルの構成",[20,4708,4709],{},"初期のファイル構成は以下の様になっています。",[165,4711,4713],{"className":1786,"code":4712,"language":1788,"meta":173,"style":173},"├── db.sqlite3\n├── djangodemo\n│   ├── __init__.py\n│   ├── asgi.py\n│   ├── settings.py\n│   ├── urls.py\n│   └── wsgi.py\n├── manage.py\n",[138,4714,4715,4723,4730,4741,4750,4759,4768,4778],{"__ignoreMap":173},[184,4716,4717,4720],{"class":186,"line":187},[184,4718,4719],{"class":1795},"├──",[184,4721,4722],{"class":236}," db.sqlite3\n",[184,4724,4725,4727],{"class":186,"line":193},[184,4726,4719],{"class":1795},[184,4728,4729],{"class":236}," djangodemo\n",[184,4731,4732,4735,4738],{"class":186,"line":251},[184,4733,4734],{"class":1795},"│",[184,4736,4737],{"class":236},"   ├──",[184,4739,4740],{"class":236}," __init__.py\n",[184,4742,4743,4745,4747],{"class":186,"line":259},[184,4744,4734],{"class":1795},[184,4746,4737],{"class":236},[184,4748,4749],{"class":236}," asgi.py\n",[184,4751,4752,4754,4756],{"class":186,"line":271},[184,4753,4734],{"class":1795},[184,4755,4737],{"class":236},[184,4757,4758],{"class":236}," settings.py\n",[184,4760,4761,4763,4765],{"class":186,"line":279},[184,4762,4734],{"class":1795},[184,4764,4737],{"class":236},[184,4766,4767],{"class":236}," urls.py\n",[184,4769,4770,4772,4775],{"class":186,"line":4},[184,4771,4734],{"class":1795},[184,4773,4774],{"class":236},"   └──",[184,4776,4777],{"class":236}," wsgi.py\n",[184,4779,4780,4782],{"class":186,"line":305},[184,4781,4719],{"class":1795},[184,4783,4784],{"class":236}," manage.py\n",[20,4786,4787,4790,4791,4793,4794,159,4797,4800],{},[138,4788,4789],{},"djangodemo\u002F"," はDjangoのアプリを最初に作成した時に生成されるディレクトリです。",[138,4792,4789],{}," では特に ",[138,4795,4796],{},"settings.py",[138,4798,4799],{},"urls.py","が重要です。",[342,4802,4803],{"id":4803},"設定ファイル",[20,4805,4806,4808,4809,4812,4813,4816],{},[138,4807,4796],{}," はLaravelでいう",[138,4810,4811],{},"config\u002F","ディレクトリ配下のファイルたちを一つにまとめた様なものになっています。特に",[138,4814,4815],{},"config\u002Fapp.php","の記述が近いかもしれません。一部抜粋して解説します。",[165,4818,4822],{"className":4819,"code":4820,"filename":4796,"language":4821,"meta":173,"style":173},"language-python shiki shiki-themes material-theme-ocean","BASE_DIR = Path(__file__).resolve().parent.parent\n\nSECRET_KEY = 'hogehoge'\n\n# SECURITY WARNING: don't run with debug turned on in production!\n# Laravelのapp.debugと同じ\nDEBUG = True\n\n# Application definition\n# Djangoが利用可能なアプリ（モジュール）Laravelにはないかも。\nINSTALLED_APPS = [\n    'django.contrib.admin',\n    'django.contrib.auth',\n    'django.contrib.contenttypes',\n    'django.contrib.sessions',\n    'django.contrib.messages',\n    'django.contrib.staticfiles',\n]\n\n# laravel の app\u002FHttp\u002FKernel.php のmiddlewareみたいなもの\nMIDDLEWARE = [\n    'django.middleware.security.SecurityMiddleware',\n    'django.contrib.sessions.middleware.SessionMiddleware',\n    'django.middleware.common.CommonMiddleware',\n    'django.middleware.csrf.CsrfViewMiddleware',\n    'django.contrib.auth.middleware.AuthenticationMiddleware',\n    'django.contrib.messages.middleware.MessageMiddleware',\n    'django.middleware.clickjacking.XFrameOptionsMiddleware',\n]\n\n# Laravelのroutesみたいなもの。\nROOT_URLCONF = 'djangodemo.urls'\n\n# Laravelのビューテンプレートが resource\u002Fviewであることを決めている様なことと同じ。DjangoないのTemplateの読み込み先を定義している\nTEMPLATES = [\n    # ...\n]\n\nWSGI_APPLICATION = 'djangodemo.wsgi.application'\n\n# Laravelのconfig\u002Fdatabase.phpと同じ。使用するDBドライバやアクセス情報を記述。\nDATABASES = {\n    'default': {\n        'ENGINE': 'django.db.backends.mysql',\n        'NAME': 'preform',\n        'USER': 'root',\n        'PASSWORD': 'rootroot',\n        'HOST': 'db',\n        'PORT': '3306',\n    }\n}\n\n# 静的ファイルを配置する箇所。Laravelのstorageディレクトリ を定義している様なもの。\nSTATIC_URL = '\u002Fstatic\u002F'\n\n","python",[138,4823,4824,4829,4833,4838,4842,4847,4852,4857,4861,4866,4871,4876,4881,4886,4891,4896,4901,4906,4910,4914,4919,4924,4929,4934,4939,4944,4949,4954,4959,4963,4967,4972,4977,4981,4986,4991,4996,5000,5004,5009,5013,5018,5023,5028,5033,5038,5043,5048,5053,5058,5062,5066,5070,5075],{"__ignoreMap":173},[184,4825,4826],{"class":186,"line":187},[184,4827,4828],{},"BASE_DIR = Path(__file__).resolve().parent.parent\n",[184,4830,4831],{"class":186,"line":193},[184,4832,367],{"emptyLinePlaceholder":366},[184,4834,4835],{"class":186,"line":251},[184,4836,4837],{},"SECRET_KEY = 'hogehoge'\n",[184,4839,4840],{"class":186,"line":259},[184,4841,367],{"emptyLinePlaceholder":366},[184,4843,4844],{"class":186,"line":271},[184,4845,4846],{},"# SECURITY WARNING: don't run with debug turned on in production!\n",[184,4848,4849],{"class":186,"line":279},[184,4850,4851],{},"# Laravelのapp.debugと同じ\n",[184,4853,4854],{"class":186,"line":4},[184,4855,4856],{},"DEBUG = True\n",[184,4858,4859],{"class":186,"line":305},[184,4860,367],{"emptyLinePlaceholder":366},[184,4862,4863],{"class":186,"line":313},[184,4864,4865],{},"# Application definition\n",[184,4867,4868],{"class":186,"line":321},[184,4869,4870],{},"# Djangoが利用可能なアプリ（モジュール）Laravelにはないかも。\n",[184,4872,4873],{"class":186,"line":329},[184,4874,4875],{},"INSTALLED_APPS = [\n",[184,4877,4878],{"class":186,"line":412},[184,4879,4880],{},"    'django.contrib.admin',\n",[184,4882,4883],{"class":186,"line":417},[184,4884,4885],{},"    'django.contrib.auth',\n",[184,4887,4888],{"class":186,"line":423},[184,4889,4890],{},"    'django.contrib.contenttypes',\n",[184,4892,4893],{"class":186,"line":429},[184,4894,4895],{},"    'django.contrib.sessions',\n",[184,4897,4898],{"class":186,"line":435},[184,4899,4900],{},"    'django.contrib.messages',\n",[184,4902,4903],{"class":186,"line":441},[184,4904,4905],{},"    'django.contrib.staticfiles',\n",[184,4907,4908],{"class":186,"line":447},[184,4909,2927],{},[184,4911,4912],{"class":186,"line":453},[184,4913,367],{"emptyLinePlaceholder":366},[184,4915,4916],{"class":186,"line":459},[184,4917,4918],{},"# laravel の app\u002FHttp\u002FKernel.php のmiddlewareみたいなもの\n",[184,4920,4921],{"class":186,"line":464},[184,4922,4923],{},"MIDDLEWARE = [\n",[184,4925,4926],{"class":186,"line":470},[184,4927,4928],{},"    'django.middleware.security.SecurityMiddleware',\n",[184,4930,4931],{"class":186,"line":476},[184,4932,4933],{},"    'django.contrib.sessions.middleware.SessionMiddleware',\n",[184,4935,4936],{"class":186,"line":481},[184,4937,4938],{},"    'django.middleware.common.CommonMiddleware',\n",[184,4940,4941],{"class":186,"line":487},[184,4942,4943],{},"    'django.middleware.csrf.CsrfViewMiddleware',\n",[184,4945,4946],{"class":186,"line":493},[184,4947,4948],{},"    'django.contrib.auth.middleware.AuthenticationMiddleware',\n",[184,4950,4951],{"class":186,"line":499},[184,4952,4953],{},"    'django.contrib.messages.middleware.MessageMiddleware',\n",[184,4955,4956],{"class":186,"line":505},[184,4957,4958],{},"    'django.middleware.clickjacking.XFrameOptionsMiddleware',\n",[184,4960,4961],{"class":186,"line":511},[184,4962,2927],{},[184,4964,4965],{"class":186,"line":517},[184,4966,367],{"emptyLinePlaceholder":366},[184,4968,4969],{"class":186,"line":523},[184,4970,4971],{},"# Laravelのroutesみたいなもの。\n",[184,4973,4974],{"class":186,"line":528},[184,4975,4976],{},"ROOT_URLCONF = 'djangodemo.urls'\n",[184,4978,4979],{"class":186,"line":533},[184,4980,367],{"emptyLinePlaceholder":366},[184,4982,4983],{"class":186,"line":538},[184,4984,4985],{},"# Laravelのビューテンプレートが resource\u002Fviewであることを決めている様なことと同じ。DjangoないのTemplateの読み込み先を定義している\n",[184,4987,4988],{"class":186,"line":543},[184,4989,4990],{},"TEMPLATES = [\n",[184,4992,4993],{"class":186,"line":549},[184,4994,4995],{},"    # ...\n",[184,4997,4998],{"class":186,"line":555},[184,4999,2927],{},[184,5001,5002],{"class":186,"line":561},[184,5003,367],{"emptyLinePlaceholder":366},[184,5005,5006],{"class":186,"line":566},[184,5007,5008],{},"WSGI_APPLICATION = 'djangodemo.wsgi.application'\n",[184,5010,5011],{"class":186,"line":572},[184,5012,367],{"emptyLinePlaceholder":366},[184,5014,5015],{"class":186,"line":578},[184,5016,5017],{},"# Laravelのconfig\u002Fdatabase.phpと同じ。使用するDBドライバやアクセス情報を記述。\n",[184,5019,5020],{"class":186,"line":584},[184,5021,5022],{},"DATABASES = {\n",[184,5024,5025],{"class":186,"line":589},[184,5026,5027],{},"    'default': {\n",[184,5029,5030],{"class":186,"line":595},[184,5031,5032],{},"        'ENGINE': 'django.db.backends.mysql',\n",[184,5034,5035],{"class":186,"line":600},[184,5036,5037],{},"        'NAME': 'preform',\n",[184,5039,5040],{"class":186,"line":606},[184,5041,5042],{},"        'USER': 'root',\n",[184,5044,5045],{"class":186,"line":612},[184,5046,5047],{},"        'PASSWORD': 'rootroot',\n",[184,5049,5050],{"class":186,"line":617},[184,5051,5052],{},"        'HOST': 'db',\n",[184,5054,5055],{"class":186,"line":623},[184,5056,5057],{},"        'PORT': '3306',\n",[184,5059,5060],{"class":186,"line":628},[184,5061,520],{},[184,5063,5064],{"class":186,"line":634},[184,5065,400],{},[184,5067,5068],{"class":186,"line":639},[184,5069,367],{"emptyLinePlaceholder":366},[184,5071,5072],{"class":186,"line":645},[184,5073,5074],{},"# 静的ファイルを配置する箇所。Laravelのstorageディレクトリ を定義している様なもの。\n",[184,5076,5078],{"class":186,"line":5077},54,[184,5079,5080],{},"STATIC_URL = '\u002Fstatic\u002F'\n",[342,5082,5083],{"id":5083},"ルーティング",[20,5085,5086,5087,5089,5090,5093,5094,5097,5098,5101],{},"次は",[138,5088,4799],{},"です。これはLaravelでいうところの ",[138,5091,5092],{},"routes\u002F","と内容的には一緒です。ここでHTTPリクエストに対する処理を結びつけています。Urlディスパッチャと言われています。Laravelでは",[138,5095,5096],{},"web.php","や",[138,5099,5100],{},"api.php","などいくらか分かれていますが、Djangoではデフォルトで一つです。詳細な記述は次回説明します。",[342,5103,5104],{"id":5104},"アプリ側のディレクトリ",[20,5106,5107,5108,5111],{},"次にアプリを作成します。Djangoは機能ごとにディレクトリを分割しアプリ(app)と呼んでいます。例としてユーザーのアカウント設定や認証を行う機能を作成するために、",[138,5109,5110],{},"account","というアプリを作成します。",[165,5113,5116],{"className":5114,"code":5115,"language":170},[168],"python3 manage.py startapp account\n",[138,5117,5115],{"__ignoreMap":173},[20,5119,5120,5121,5124],{},"ディレクトリ が一つ増えて、",[138,5122,5123],{},"account\u002F","というのが作成されたはずです。アプリ内には初期では以下の通りテンプレートが作成されます。",[165,5126,5129],{"className":5127,"code":5128,"language":170},[168],"├── account\n│   ├── __init__.py\n│   ├── admin.py\n│   ├── apps.py\n│   ├── migrations\n│   │   └── __init__.py\n│   ├── models.py\n│   ├── tests.py\n│   └── views.py\n├── db.sqlite3\n├── djangodemo\n├── manage.py\n",[138,5130,5128],{"__ignoreMap":173},[20,5132,5133,5134,5137,5138,5140],{},"Laravelは",[138,5135,5136],{},"app\u002F","配下にコントローラーやモデル、ジョブなどのクラスファイルを定義します。独立した機能であっても",[138,5139,5136],{},"配下に配置してクラス名などで判別する感じです。",[20,5142,5143,5144,5147],{},"しかしDjangoは",[68,5145,5146],{},"機能ごとにディレクトリ を作成し、その機能に関連するモデル、ビュー、テストの設定を記述","します。ここがLaravelとすこし違うところです。",[20,5149,5150,5151,5154,5155,5158],{},"アプリを",[138,5152,5153],{},"setting.py","の",[138,5156,5157],{},"INSTALLED_APPS","に記述することで、",[165,5160,5162],{"className":4819,"code":5161,"language":4821,"meta":173,"style":173},"from account.model import Account\n\n# account\u002Fmodel.py のAccountクラスを使用する\n",[138,5163,5164,5169,5173],{"__ignoreMap":173},[184,5165,5166],{"class":186,"line":187},[184,5167,5168],{},"from account.model import Account\n",[184,5170,5171],{"class":186,"line":193},[184,5172,367],{"emptyLinePlaceholder":366},[184,5174,5175],{"class":186,"line":251},[184,5176,5177],{},"# account\u002Fmodel.py のAccountクラスを使用する\n",[20,5179,5180,5181,5183],{},"などそのアプリ内機能をモジュールとして使用できます。Laravel（PHP）風に解説すると、適切な名前空間を定義して任意のファイルで",[138,5182,3306],{},"する感じです。",[165,5185,5187],{"className":2519,"code":5186,"language":2522,"meta":173,"style":173},"use App\\Models\\Account\n",[138,5188,5189],{"__ignoreMap":173},[184,5190,5191],{"class":186,"line":187},[184,5192,5186],{},[20,5194,5195],{},"それぞれのファイルを説明します。",[34,5197,5198,5204,5213,5222,5231,5237],{},[37,5199,5200,5203],{},[138,5201,5202],{},"admin.py",":Djangoが提供する管理者画面でモデルの内容をどう表記するかを定義します。",[37,5205,5206,5209,5210,5212],{},[138,5207,5208],{},"apps.py",":アプリの設定ファイル。アプリの名前やデフォルトのプライマリーキーなどを設定でき、",[138,5211,5157],{},"に必要",[37,5214,5215,5218,5219],{},[138,5216,5217],{},"models.py",": アプリのモデルファイル。テーブルや使用するフィールドの定義などを行う。Laravelでいう",[138,5220,5221],{},"app\u002Fmodels",[37,5223,5224,5227,5228],{},[138,5225,5226],{},"tets.py",": アプリのテストファイル。テストコードを記述する。Laravelでいう",[138,5229,5230],{},"tests\u002F",[37,5232,5233,5236],{},[138,5234,5235],{},"views.py",": アプリのビューファイル。DjangoではビューはLaravelでいうControllerみたいな働きをする。",[37,5238,5239,5242,5243,754],{},[138,5240,5241],{},"migrations\u002F",": DBに対する変更、マイグレーションファイルが格納されます。Laravelでいう、",[138,5244,5245],{},"database\u002Fmigrations",[20,5247,5248,5249,5252],{},"のこっている ",[138,5250,5251],{},"__init__.py","ですがこれはpythonがディレクトリ をパッケージとして認識して、importできる様にするために必要なファイルです。python2ではこのファイルがないとimportが動作しません。python3からは不要ですが、後方互換性のために作っておいて損はありません。",[165,5254,5256],{"className":4819,"code":5255,"language":4821,"meta":173,"style":173},"from account.models\n\"\"\"\naccountディレクトリ（パッケージ）のmodels.py（モジュール）を読み込むということが,\n__init__.pyがあるとできる。\n\"\"\"\n",[138,5257,5258,5263,5268,5273,5278],{"__ignoreMap":173},[184,5259,5260],{"class":186,"line":187},[184,5261,5262],{},"from account.models\n",[184,5264,5265],{"class":186,"line":193},[184,5266,5267],{},"\"\"\"\n",[184,5269,5270],{"class":186,"line":251},[184,5271,5272],{},"accountディレクトリ（パッケージ）のmodels.py（モジュール）を読み込むということが,\n",[184,5274,5275],{"class":186,"line":259},[184,5276,5277],{},"__init__.pyがあるとできる。\n",[184,5279,5280],{"class":186,"line":271},[184,5281,5267],{},[27,5283,5284],{"id":5284},"今回はとりあえずここまで",[20,5286,5287],{},"ひとまずこの回ではディレクトリやファイルの説明までとしておきます。次回はユーザーモデルを使用した認証の実装とLaravelと比較したCRUDの一部（記事の作成）を実装します。",[1530,5289,5290],{},"html pre.shiki code .s5Dmg, html code.shiki .s5Dmg{--shiki-default:#FFCB6B}html pre.shiki code .sfyAc, html code.shiki .sfyAc{--shiki-default:#C3E88D}html pre.shiki code .sdLwU, html code.shiki .sdLwU{--shiki-default:#82AAFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s-wAU, html code.shiki .s-wAU{--shiki-default:#F07178}html pre.shiki code .sAklC, html code.shiki .sAklC{--shiki-default:#89DDFF}html pre.shiki code .s0W1g, html code.shiki .s0W1g{--shiki-default:#BABED8}html pre.shiki code .sx098, html code.shiki .sx098{--shiki-default:#F78C6C}html pre.shiki code .sC9rS, html code.shiki .sC9rS{--shiki-default:#464B5D;--shiki-default-font-style:italic}",{"title":173,"searchDepth":251,"depth":251,"links":5292},[5293,5297,5302,5310],{"id":1753,"depth":193,"text":1753,"children":5294},[5295,5296],{"id":4046,"depth":251,"text":158},{"id":2372,"depth":251,"text":2372},{"id":4476,"depth":193,"text":4476,"children":5298},[5299,5300,5301],{"id":1653,"depth":259,"text":1653},{"id":1687,"depth":259,"text":1687},{"id":1727,"depth":259,"text":1727},{"id":4553,"depth":193,"text":4554,"children":5303},[5304,5305],{"id":4560,"depth":251,"text":4560},{"id":4706,"depth":251,"text":4706,"children":5306},[5307,5308,5309],{"id":4803,"depth":259,"text":4803},{"id":5083,"depth":259,"text":5083},{"id":5104,"depth":259,"text":5104},{"id":5284,"depth":193,"text":5284},[1561],"2022-02-12",{},"\u002Fseries\u002Flaravel-to-django-1",{"title":3911,"description":3899},"laravel_to_django","Laravel使いがDjangoでwebアプリを作るよ","series\u002Flaravel-to-django-1",[5320,4821,2464,2522],"django","laralve_to_django\u002Fthumbnail.png","k4TYlI4xXfHM5YMh4HtklCzEfD1cyqgLf2FCDeypoBY",{"id":5324,"title":5325,"body":5326,"category":6157,"createdAt":6158,"description":6159,"extension":1564,"index":187,"meta":6160,"navigation":366,"path":6161,"publish":366,"seo":6162,"series":6163,"seriesTitle":6164,"stem":6165,"tag":6166,"thumbnail":6167,"updatedAt":1575,"__hash__":6168},"series\u002Fseries\u002Fnuxt-content-blog-1.md","Nuxt Content × SSG で作る静的ブログ。１：概念とセットアップ",{"type":10,"value":5327,"toc":6142},[5328,5331,5338,5355,5358,5362,5369,5375,5378,5381,5384,5401,5404,5406,5420,5427,5446,5449,5452,5455,5563,5592,5594,5637,5640,5642,5645,5669,5672,5675,5678,5681,5687,5704,5715,5765,5771,5777,5783,5806,5809,5812,5817,5870,5877,5880,5886,6067,6070,6074,6085,6088,6100,6124,6127,6133,6139],[20,5329,5330],{},"こんにちはjunです。私のブログは2021年4月にwordpressを卒業し、Nuxt contentというものを使用してリニューアルしました。wordpressは簡単にサイトは作れますし使い勝手はいいですが、公開サーバーにCMSがあるためにセキュリティ的に問題だったり、大量に記事があるとパフォーマンスが落ちるということがあります。webデベロッパーならばNuxt.jsと静的書き出しぐらいやろうぜ！と思ったのも理由です。",[20,5332,5333,5334,5337],{},"このブログリニューアルにはデータ移行含めて２週間ほどかかりましたが、その途中で少し詰まったり工夫したりした箇所が結構あったのでシリーズ記事として、 ",[68,5335,5336],{},"「Nuxt Contentによる静的書き出しブログの作成方法」"," として書きたいと思います。シリーズを通して以下のことを解説しようと思います。（まだ予定です。もしかすると執筆途中で変わります。）",[98,5339,5340,5343,5346,5349,5352],{},[37,5341,5342],{},"Nuxt Content の仕組みと基本的な使い方",[37,5344,5345],{},"記事のレンダリングと静的書き出しルーティング",[37,5347,5348],{},"記事一覧ページとページング実装",[37,5350,5351],{},"カテゴリーとタグ機能",[37,5353,5354],{},"記事の管理とデプロイ。SEO対策。アドセンスとアナリティクス設定",[20,5356,5357],{},"それでは一回目は早速、インストールから概念・基本的な使い方について説明していきます。Nuxt.jsにおける構成などは共有しますが、このサイトにおけるデザインの実装方法などは省略しますのでご了承ください。",[27,5359,5361],{"id":5360},"nuxt-contentとは","Nuxt Contentとは？",[20,5363,5364,5365,5368],{},"nuxt contentはNuxt.jsのモジュールです。",[138,5366,5367],{},"content\u002F","ディレクトリ配下にmarkdownを用いて記事原稿を作成し、Nuxt.jsがmarkdownを解析してオブジェクトとして利用できるようにしてくれます。",[20,5370,5371,5374],{},[138,5372,5373],{},"$content","というインスタンスがグローバルに使用できますので、それらを利用してコンポーネントにレンダーするという仕組みです。",[20,5376,5377],{},"ブログを構成するソースが全てファイルで構成されるので、GitベースのCMSとして利用することができます。（記事の保存にDBを必要としない。）",[27,5379,5380],{"id":5380},"自分のブログの構成について",[20,5382,5383],{},"今回のシリーズ記事では私の記事と同じ構成を作成できるように解説していこうと思います。2021年5月現在では以下のような構成・機能を持っています。",[34,5385,5386,5389,5392,5395,5398],{},[37,5387,5388],{},"記事詳細ページ",[37,5390,5391],{},"タグ・カテゴリー",[37,5393,5394],{},"各々の一覧ページとページング",[37,5396,5397],{},"シリーズ記事",[37,5399,5400],{},"静的書き出しとデプロイ",[20,5402,5403],{},"詳細に解説します。",[47,5405,5388],{"id":5388},[20,5407,5408,5409,5412,5413,5416,5417,5419],{},"私のサイトでは",[138,5410,5411],{},"\u002Farticles\u002F{sulg}","、",[138,5414,5415],{},"\u002Fseries\u002F{sulg}\u002F{index}","というルートにて各記事本体を見ることができ、そのようなルールで構成されています。スラグ（sulg）は原稿マークダウンのファイル名と一致します。ソースでは",[138,5418,5367],{},"ディレクトリ配下にマークダウンファイルを以下のように格納しています。",[165,5421,5425],{"className":5422,"code":5423,"filename":5424,"language":170,"meta":173},[168],"├── content\n│   ├── articles\n│   │   ├── aaaaaa.md\n│   │   ├── bbbbbb.md\n│   │\n│   └── series\n│       ├── ~~~~~.md\n│       ├── ~~~~~.md\n","Nuxt",[138,5426,5423],{"__ignoreMap":173},[20,5428,5429,5430,5433,5434,5437,5438,5441,5442,5445],{},"上記のように格納することでコンポーネントで、例えば",[138,5431,5432],{},"this.$content('articles')","として呼び出すことでarticles配下のデータを取得できます。ファイル名＝スラグ名としているので、",[138,5435,5436],{},"\u002Farticles\u002Faaaaaaa","とすることで、",[138,5439,5440],{},"aaaaaa.md","の内容がレンダーされるようにしています。このへんのルーティング設定は",[138,5443,5444],{},"page\u002F","ディレクトリの構成で制御しています。第二回目で解説します。",[47,5447,5391],{"id":5448},"タグカテゴリー",[20,5450,5451],{},"各記事ではタグとカテゴリーを指定できるようになっています。カテゴリー、タグをクリックするとその一覧に飛ぶのでユーザーはサイトのコンテンツを探しやすくなります。",[20,5453,5454],{},"タグとカテゴリーは実はマークダウンファイルに記述されています。例えばこの記事のマークダウンには一番最初に以下のようなyml形式の記述があります。",[165,5456,5460],{"className":5457,"code":5458,"language":5459,"meta":173,"style":173},"language-yml shiki shiki-themes material-theme-ocean","---\ntitle: Nuxt Content × SSG で作る静的ブログ。１：概念とセットアップ\ndescription: Nuxt Content × SSG で作る静的ブログ。概念とセットアップについてまずは解説\ncategory: [devstack]\ntag: [js,nuxt]\nseries: nuxt-content-blog\nseriesTitle: Nuxt Content × SSG で作る静的ブログ。\nindex: 1\npublish: false\n---\n","yml",[138,5461,5462,5467,5476,5486,5500,5518,5528,5538,5548,5559],{"__ignoreMap":173},[184,5463,5464],{"class":186,"line":187},[184,5465,5466],{"class":1795},"---\n",[184,5468,5469,5471,5473],{"class":186,"line":193},[184,5470,1050],{"class":225},[184,5472,230],{"class":229},[184,5474,5475],{"class":236}," Nuxt Content × SSG で作る静的ブログ。１：概念とセットアップ\n",[184,5477,5478,5481,5483],{"class":186,"line":251},[184,5479,5480],{"class":225},"description",[184,5482,230],{"class":229},[184,5484,5485],{"class":236}," Nuxt Content × SSG で作る静的ブログ。概念とセットアップについてまずは解説\n",[184,5487,5488,5491,5493,5496,5498],{"class":186,"line":259},[184,5489,5490],{"class":225},"category",[184,5492,230],{"class":229},[184,5494,5495],{"class":229}," [",[184,5497,1561],{"class":236},[184,5499,2927],{"class":229},[184,5501,5502,5505,5507,5509,5511,5513,5516],{"class":186,"line":271},[184,5503,5504],{"class":225},"tag",[184,5506,230],{"class":229},[184,5508,5495],{"class":229},[184,5510,2752],{"class":236},[184,5512,1267],{"class":229},[184,5514,5515],{"class":236},"nuxt",[184,5517,2927],{"class":229},[184,5519,5520,5523,5525],{"class":186,"line":279},[184,5521,5522],{"class":225},"series",[184,5524,230],{"class":229},[184,5526,5527],{"class":236}," nuxt-content-blog\n",[184,5529,5530,5533,5535],{"class":186,"line":4},[184,5531,5532],{"class":225},"seriesTitle",[184,5534,230],{"class":229},[184,5536,5537],{"class":236}," Nuxt Content × SSG で作る静的ブログ。\n",[184,5539,5540,5543,5545],{"class":186,"line":305},[184,5541,5542],{"class":225},"index",[184,5544,230],{"class":229},[184,5546,5547],{"class":267}," 1\n",[184,5549,5550,5553,5555],{"class":186,"line":313},[184,5551,5552],{"class":225},"publish",[184,5554,230],{"class":229},[184,5556,5558],{"class":5557},"sbqyR"," false\n",[184,5560,5561],{"class":186,"line":321},[184,5562,5466],{"class":1795},[20,5564,5565,5566,5412,5569,5412,5571,5573,5574,5412,5576,5412,5579,5582,5583,3457,5585,5587,5588,5591],{},"上記のカラムは自由につけることができます。例えば",[138,5567,5568],{},"serise",[138,5570,5542],{},[138,5572,5552],{},"は私が独自につけています。一方で",[138,5575,1050],{},[138,5577,5578],{},"updateAt",[138,5580,5581],{},"createdAt","など自動的に付与される属性もあります。",[138,5584,5504],{},[138,5586,5490],{},"は配列形式の記述をすることでjs側でも配列で扱うことができます。",[138,5589,5590],{},"taxonomy.js","というファイルでキーとカテゴリー名、タグを管理しています。（storeでもOKですが、ちょっと困ることがありました。こちらも後で解説します。）",[47,5593,5394],{"id":5394},[34,5595,5596,5601,5606,5611,5617,5622,5627,5632],{},[37,5597,5598],{},[138,5599,5600],{},"\u002Farticles\u002F",[37,5602,5603],{},[138,5604,5605],{},"\u002Fseries\u002F{sulg}\u002F",[37,5607,5608],{},[138,5609,5610],{},"\u002Fcategory\u002F{category_key}\u002F",[37,5612,5613,5616],{},[138,5614,5615],{},"\u002Ftag\u002F{tag_key}\u002F","\nというパスではその記事種別、カテゴリーの一覧が表示されます。１ページあたり15記事表示されるので記事が多くなるとページングが発生します。ページングの際は",[37,5618,5619],{},[138,5620,5621],{},"\u002Farticles\u002Fpage\u002F{n}",[37,5623,5624],{},[138,5625,5626],{},"\u002Fseries\u002F{sulg}\u002Fpage\u002F{n}",[37,5628,5629],{},[138,5630,5631],{},"\u002Fcategory\u002F{category_key}\u002Fpage\u002F{n}",[37,5633,5634],{},[138,5635,5636],{},"\u002Ftag\u002F{tag_key}\u002Fpage\u002F{n}",[20,5638,5639],{},"というルーティングで現在ページを判別しています。ちなみに静的書き出しするときはページごとにディレクトリが作成されます。",[47,5641,5400],{"id":5400},[20,5643,5644],{},"私の場合、まずNuxt.jsには静的書き出し（SSG:static site generate）を使用して作成してページ分のHTMLを生成します。そして生成されたHTMLと画像をrsyncで公開サーバーに送っています。作成全体の流れを簡単に説明しますと、",[98,5646,5647,5650,5653,5656,5662],{},[37,5648,5649],{},"markdownで原稿を記述",[37,5651,5652],{},"pageコンポーネントに原稿内容をレンダーする。",[37,5654,5655],{},"公開してよい原稿のみをクエリで取得してルーティングを設定する。",[37,5657,5658,5661],{},[138,5659,5660],{},"npm run generate"," を使用して作成した原稿分のHTMLを生成する",[37,5663,5664,5665,5668],{},"rsyncを使用して",[138,5666,5667],{},"\u002Fdist","配下を公開サーバーを同期する",[20,5670,5671],{},"上記のような感じです。結構簡単です。",[20,5673,5674],{},"以上が簡単な概念と構成の説明です。それでは以降からは具体的なインストールと使い方を説明していきます。",[27,5676,5677],{"id":5677},"インストール方法とセットアップ",[20,5679,5680],{},"Nuxt contentはNuxt.jsのモジュールですのでまずはNuxtプロジェクトを作成します。",[165,5682,5685],{"className":5683,"code":5684,"language":170},[168],"npx create-nuxt-app \u003Cproject-name>\n\n... #普通にNuxtのインストールをする\n\nnpm install @nuxt\u002Fcontent\n\n",[138,5686,5684],{"__ignoreMap":173},[13,5688,5691,5692],{"className":5689},[16,5690],"alert-success","\n各種の使用バージョンは以下の通りです。\n",[34,5693,5694,5695,5694,5698,5694,5701],{},"\n    ",[37,5696,5697],{},"Node.js：v12.19.0",[37,5699,5700],{},"Nuxt.js：2.14.12",[37,5702,5703],{},"Nuxt Content：1.14.0",[20,5705,5706,5707,5710,5711,5714],{},"インストールが終わったので、",[138,5708,5709],{},"nuxt.config.js","も",[138,5712,5713],{},"modules","に以下のように追記します。",[165,5716,5718],{"className":2749,"code":5717,"filename":5709,"language":2752,"meta":173,"style":173},"  ...\n\n  modules: [\n    '@nuxt\u002Fcontent',\n  ],\n\n  ...\n",[138,5719,5720,5725,5729,5738,5750,5757,5761],{"__ignoreMap":173},[184,5721,5722],{"class":186,"line":187},[184,5723,5724],{"class":229},"  ...\n",[184,5726,5727],{"class":186,"line":193},[184,5728,367],{"emptyLinePlaceholder":366},[184,5730,5731,5734,5736],{"class":186,"line":251},[184,5732,5733],{"class":1795},"  modules",[184,5735,230],{"class":229},[184,5737,2869],{"class":1056},[184,5739,5740,5743,5746,5748],{"class":186,"line":259},[184,5741,5742],{"class":229},"    '",[184,5744,5745],{"class":236},"@nuxt\u002Fcontent",[184,5747,1260],{"class":229},[184,5749,3457],{"class":229},[184,5751,5752,5755],{"class":186,"line":271},[184,5753,5754],{"class":1056},"  ]",[184,5756,3457],{"class":229},[184,5758,5759],{"class":186,"line":279},[184,5760,367],{"emptyLinePlaceholder":366},[184,5762,5763],{"class":186,"line":4},[184,5764,5724],{"class":229},[20,5766,5767,5768,5770],{},"そして",[138,5769,5367],{},"というディレクトリをプロジェクトルートに作成します。",[165,5772,5775],{"className":5773,"code":5774,"language":170},[168],".\n├── README.md\n├── assets\n├── components\n├── content # これをつくる\n├── layouts\n├── middleware\n├── node_modules\n├── nuxt.config.js\n├── package-lock.json\n├── package.json\n├── pages\n├── plugins\n├── static\n├── store\n",[138,5776,5774],{"__ignoreMap":173},[20,5778,5779,5780,5782],{},"マークダウンファイルは",[138,5781,5367],{},"配下に作成していくと、nuxt contentは自動的にそれらのファイルを解析してくれます。",[20,5784,5767,5785,5787,5788,5791,5792,5795,5796,5799,5800,159,5802,5805],{},[138,5786,5367],{},"ディレクトリを作成したらさらに",[138,5789,5790],{},"articles\u002F","といったディレクトリを作っておくと良いです。別に",[138,5793,5794],{},"articles","としなくてもいいですが、サブディレクトリ を作ることで",[138,5797,5798],{},"$content('articles')","のように区別してコンテンツを取得できます。私の場合は",[138,5801,5790],{},[138,5803,5804],{},"series\u002F","というサブディレクトリでコンテンツを区切っています。",[27,5807,5808],{"id":5808},"記事を試しに作成してみる",[47,5810,5811],{"id":5811},"マークダウン原稿を作成する",[20,5813,2733,5814,5816],{},[138,5815,5790],{},"を作ったら何かマークダウンを作成してみましょう。",[165,5818,5823],{"className":5819,"code":5820,"filename":5821,"language":5822,"meta":173,"style":173},"language-markdown shiki shiki-themes material-theme-ocean","---\ntitle: テスト\ndescription:テスト\n---\nここに記事内容をマークダウン で記述します。\n\n## 見出し\n- リスト\n- リスト\n- リスト\n","articles\u002Ftest.md","markdown",[138,5824,5825,5829,5834,5839,5843,5848,5852,5857,5862,5866],{"__ignoreMap":173},[184,5826,5827],{"class":186,"line":187},[184,5828,5466],{},[184,5830,5831],{"class":186,"line":193},[184,5832,5833],{},"title: テスト\n",[184,5835,5836],{"class":186,"line":251},[184,5837,5838],{},"description:テスト\n",[184,5840,5841],{"class":186,"line":259},[184,5842,5466],{},[184,5844,5845],{"class":186,"line":271},[184,5846,5847],{},"ここに記事内容をマークダウン で記述します。\n",[184,5849,5850],{"class":186,"line":279},[184,5851,367],{"emptyLinePlaceholder":366},[184,5853,5854],{"class":186,"line":4},[184,5855,5856],{},"## 見出し\n",[184,5858,5859],{"class":186,"line":305},[184,5860,5861],{},"- リスト\n",[184,5863,5864],{"class":186,"line":313},[184,5865,5861],{},[184,5867,5868],{"class":186,"line":321},[184,5869,5861],{},[20,5871,5872,5873,5876],{},"まずnuxt contentでマークダウン原稿を作る際には、１・２行目に示されたように",[138,5874,5875],{},"---","で囲ったymlにて書かれたメタデータを記述します。メタデータをかけたら、マークダウン 形式で内容を記述していきます。",[47,5878,5879],{"id":5879},"ページコンポーネントで読み込む",[20,5881,5882,5883,5885],{},"サンプルを作成したら",[138,5884,5444],{},"ディレクトリでページコンポーネントを作成します。今回は簡単に以下のような構成にしてみます。",[165,5887,5890],{"className":2955,"code":5888,"filename":5889,"language":2701,"meta":173,"style":173},"\u003Ctemplate>\n    \u003Cdiv>\n        {{content}}\n    \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\u003Cscript>\nexport default{\n    name:'test_page',\n    async asyncData({ store,$content, params }) {\n        const content = await $content('articles').fetch();\n\n        return {\n            content,\n        }\n    },\n}\n\u003C\u002Fscript>\n","pages\u002Ftest.vue",[138,5891,5892,5900,5908,5913,5921,5929,5937,5945,5960,5992,6028,6032,6039,6046,6050,6055,6059],{"__ignoreMap":173},[184,5893,5894,5896,5898],{"class":186,"line":187},[184,5895,1002],{"class":229},[184,5897,2966],{"class":225},[184,5899,997],{"class":229},[184,5901,5902,5904,5906],{"class":186,"line":193},[184,5903,2973],{"class":229},[184,5905,13],{"class":225},[184,5907,997],{"class":229},[184,5909,5910],{"class":186,"line":251},[184,5911,5912],{"class":1056},"        {{content}}\n",[184,5914,5915,5917,5919],{"class":186,"line":259},[184,5916,3017],{"class":229},[184,5918,13],{"class":225},[184,5920,997],{"class":229},[184,5922,5923,5925,5927],{"class":186,"line":271},[184,5924,1060],{"class":229},[184,5926,2966],{"class":225},[184,5928,997],{"class":229},[184,5930,5931,5933,5935],{"class":186,"line":279},[184,5932,1002],{"class":229},[184,5934,1137],{"class":225},[184,5936,997],{"class":229},[184,5938,5939,5941,5943],{"class":186,"line":4},[184,5940,2760],{"class":993},[184,5942,2763],{"class":2759},[184,5944,3449],{"class":229},[184,5946,5947,5949,5951,5953,5956,5958],{"class":186,"line":305},[184,5948,3050],{"class":1795},[184,5950,230],{"class":229},[184,5952,1260],{"class":229},[184,5954,5955],{"class":236},"test_page",[184,5957,1260],{"class":229},[184,5959,3457],{"class":229},[184,5961,5962,5965,5968,5970,5972,5975,5977,5979,5981,5984,5987,5990],{"class":186,"line":313},[184,5963,5964],{"class":1056},"    async",[184,5966,5967],{"class":1253}," asyncData",[184,5969,1257],{"class":225},[184,5971,3403],{"class":229},[184,5973,5974],{"class":1056}," store",[184,5976,1267],{"class":229},[184,5978,5373],{"class":1056},[184,5980,1267],{"class":229},[184,5982,5983],{"class":1056}," params",[184,5985,5986],{"class":229}," }",[184,5988,5989],{"class":225},") ",[184,5991,3449],{"class":229},[184,5993,5994,5997,6000,6003,6006,6009,6011,6013,6015,6017,6019,6021,6024,6026],{"class":186,"line":321},[184,5995,5996],{"class":993},"        const",[184,5998,5999],{"class":1056}," content",[184,6001,6002],{"class":229}," =",[184,6004,6005],{"class":2759}," await",[184,6007,6008],{"class":1253}," $content",[184,6010,1257],{"class":225},[184,6012,1260],{"class":229},[184,6014,5794],{"class":236},[184,6016,1260],{"class":229},[184,6018,1294],{"class":225},[184,6020,1304],{"class":229},[184,6022,6023],{"class":1253},"fetch",[184,6025,3696],{"class":225},[184,6027,1321],{"class":229},[184,6029,6030],{"class":186,"line":329},[184,6031,367],{"emptyLinePlaceholder":366},[184,6033,6034,6037],{"class":186,"line":412},[184,6035,6036],{"class":2759},"        return",[184,6038,1270],{"class":229},[184,6040,6041,6044],{"class":186,"line":417},[184,6042,6043],{"class":1056},"            content",[184,6045,3457],{"class":229},[184,6047,6048],{"class":186,"line":423},[184,6049,514],{"class":229},[184,6051,6052],{"class":186,"line":429},[184,6053,6054],{"class":229},"    },\n",[184,6056,6057],{"class":186,"line":435},[184,6058,400],{"class":229},[184,6060,6061,6063,6065],{"class":186,"line":441},[184,6062,1060],{"class":229},[184,6064,1137],{"class":225},[184,6066,997],{"class":229},[20,6068,6069],{},"画面は以下のように映ると思います。（私の場合はたくさん記事があるので、たくさんあります。）",[87,6071],{":src":6072,":width":6073,":center":1446},"'_mix\u002Fsch-2021-05-05 8.44.11.png'","'400px'",[20,6075,6076,6077,6080,6081,6084],{},"変数",[138,6078,6079],{},"content","には",[138,6082,6083],{},"$content('articles').fetch()","によって取得されたページのデータがオブジェクト形式で入っています。",[87,6086],{":src":6087,":width":6073,":center":1446},"'_mix\u002Fsch-2021-05-05 8.48.11.png'",[20,6089,6090,1487,6092,6095,6096,6099],{},[138,6091,6083],{},[138,6093,6094],{},"aticles","配下のデータが配列でくるので、",[138,6097,6098],{},"$content('articles\u002Ftest').fetch()","としてみると単体の該当するファイルが提供されます。",[20,6101,6102,6105,6106,6111,6112,6115,6116,6119,6120,6123],{},[138,6103,6104],{},"{{content}}","ではただのオブジェクトしか表示されません。",[201,6107,6110],{"href":6108,"rel":6109},"https:\u002F\u002Fcontent.nuxtjs.org\u002Fja\u002Fdisplaying",[205],"公式サイトのように"," ",[138,6113,6114],{},"\u003Cnuxt-content :document=\"content\" \u002F>","というコンポーネントの",[138,6117,6118],{},"document","プロップスに",[138,6121,6122],{},"$content()","で取得したものを渡すことで、HTMLでレンダーされます。",[27,6125,6126],{"id":6126},"以上でセットアップ完了",[20,6128,6129,6130,6132],{},"以上がNuxt Contentのセットアップと基本的な使い方です。ひとまずモジュールをインストールして",[138,6131,6122],{},"を用いて対応するコンテンツを取得することで、ブログを作成できます。",[20,6134,6135,6136,6138],{},"次回は個別記事のレンダリングと",[138,6137,6122],{},"の詳細と静的書き出しを行っていこうと思います。",[1530,6140,6141],{},"html pre.shiki code .s5Dmg, html code.shiki .s5Dmg{--shiki-default:#FFCB6B}html pre.shiki code .s-wAU, html code.shiki .s-wAU{--shiki-default:#F07178}html pre.shiki code .sAklC, html code.shiki .sAklC{--shiki-default:#89DDFF}html pre.shiki code .sfyAc, html code.shiki .sfyAc{--shiki-default:#C3E88D}html pre.shiki code .sx098, html code.shiki .sx098{--shiki-default:#F78C6C}html pre.shiki code .sbqyR, html code.shiki .sbqyR{--shiki-default:#FF9CAC}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s0W1g, html code.shiki .s0W1g{--shiki-default:#BABED8}html pre.shiki code .sJ14y, html code.shiki .sJ14y{--shiki-default:#C792EA}html pre.shiki code .s6cf3, html code.shiki .s6cf3{--shiki-default:#89DDFF;--shiki-default-font-style:italic}html pre.shiki code .sdLwU, html code.shiki .sdLwU{--shiki-default:#82AAFF}",{"title":173,"searchDepth":251,"depth":251,"links":6143},[6144,6145,6151,6152,6156],{"id":5360,"depth":193,"text":5361},{"id":5380,"depth":193,"text":5380,"children":6146},[6147,6148,6149,6150],{"id":5388,"depth":251,"text":5388},{"id":5448,"depth":251,"text":5391},{"id":5394,"depth":251,"text":5394},{"id":5400,"depth":251,"text":5400},{"id":5677,"depth":193,"text":5677},{"id":5808,"depth":193,"text":5808,"children":6153},[6154,6155],{"id":5811,"depth":251,"text":5811},{"id":5879,"depth":251,"text":5879},{"id":6126,"depth":193,"text":6126},[1561],"2021-05-04","Nuxt Content × SSG で作る静的ブログ。概念とセットアップについてまずは解説",{},"\u002Fseries\u002Fnuxt-content-blog-1",{"title":5325,"description":6159},"nuxt-content-blog","Nuxt Content × SSG で作る静的ブログ。","series\u002Fnuxt-content-blog-1",[2752,5515],"_mix\u002Flogo-dark.jpg","NqL_rCruXq92Ga0wFzu-aNSycgZTrVDwKB90Uio1hxw",{"id":6170,"title":6171,"body":6172,"category":8768,"createdAt":8769,"description":8770,"extension":1564,"index":187,"meta":8771,"navigation":366,"path":8772,"publish":366,"seo":8773,"series":8774,"seriesTitle":8775,"stem":8776,"tag":8777,"thumbnail":8778,"updatedAt":1575,"__hash__":8779},"series\u002Fseries\u002Fwell-study-webpack-1.md","ちゃんと理解するWebpack5。１：webpack基礎とSass・jsのバンドル",{"type":10,"value":6173,"toc":8754},[6174,6178,6185,6188,6199,6202,6205,6216,6219,6222,6225,6236,6239,6242,6245,6253,6270,6273,6277,6280,6286,6289,6295,6312,6318,6321,6498,6501,6560,6576,6581,6707,6723,6731,6737,6817,6827,6833,6842,6846,6849,6851,6862,6865,6883,7015,7177,7297,7304,7320,7326,7330,7333,7336,7339,7345,7434,7443,7447,7450,7454,7457,7471,7474,7486,7492,7495,7866,7870,7873,7879,7882,7914,7969,8002,8010,8297,8303,8426,8432,8435,8677,8689,8702,8733,8736,8742,8745,8748,8751],[13,6175,6177],{"className":6176},[16,17],"\n2021年10月30日：修正事項があっため追記しました。\n",[20,6179,6180,6181,6184],{},"こんにちはjunです。最近の自社開発では息を吸うようにNuxt.jsやVue.js、React.jsなどを使用しています。私もそれらのJSライブラリをよく使用するのですが、使いは初めて１年ほどにして「もっと",[68,6182,6183],{},"JSライブラリやNode.jsの開発をしっかり理解しないと」"," という場面が増えてきました。",[20,6186,6187],{},"VueやNuxt、Reactなどは特に、ライブラリ自身のチュートリアルやインストール時のセットアップが充実しすぎてwebpack.config.jsさえ見ることもなくなりました。すぐに開発できるのが強みですが、弊害として",[34,6189,6190,6193,6196],{},[37,6191,6192],{},"実際の細かい原理や仕組みを把握できない",[37,6194,6195],{},"なにか個別にカスタマイズしようとしてもわからない",[37,6197,6198],{},"応用がわからない",[20,6200,6201],{},"といったことが生じています。フレームワークやライブラリは便利ですが、簡単でもいいので構成の概念を知っておくと細かいカスタマイズや環境構築ができるようになります。",[20,6203,6204],{},"VueやReactを使わないプロジェクトなんてざらにありますし、webpackの設定ができるだけでもフロントエンドの開発でできることが違ってきます。数回に分けてwebpackを使用した以下のパターンに分けて環境構築解説をしていきたいと思います。",[34,6206,6207,6210,6213],{},[37,6208,6209],{},"webpack基礎とSass・js",[37,6211,6212],{},"babelの導入、画像のバンドル、複数ファイル出力",[37,6214,6215],{},"htmlの取り扱いとまとめ",[20,6217,6218],{},"それではまず基礎編から初めていきます。",[27,6220,6221],{"id":6221},"webpackとは",[20,6223,6224],{},"まずwebpackとはについて解説します。webpackは静的モジュールバンドラーというもので、複数のJSファイルなどを１つのファイルにまとめることができます。1つにまとめることで",[34,6226,6227,6230,6233],{},[37,6228,6229],{},"依存性の解決（JSの読み込む順番などを気にしなくて済む）",[37,6231,6232],{},"モジュール化による保守性と安全性の向上",[37,6234,6235],{},"圧縮による軽量化",[20,6237,6238],{},"などが見込まれます。Browserifyというものなど、他にもバンドラーはありますが今は性能的、機能的にもwebpackがかなり主流になっています。",[20,6240,6241],{},"タスクランナー的な使い方も可能であり、watchして差分だけビルドして開発するなんてことも可能です。",[27,6243,6244],{"id":6244},"基礎的な概念",[20,6246,6247,6252],{},[201,6248,6251],{"href":6249,"rel":6250},"https:\u002F\u002Fwebpack.js.org\u002Fconcepts\u002F#browser-compatibility",[205],"公式ドキュメント","にもある通り、構築において以下の概念が重要となります。",[34,6254,6255,6258,6261,6264,6267],{},[37,6256,6257],{},"Entry（バンドルの起点となるファイル）",[37,6259,6260],{},"Output（バンドルファイルの吐き出し先）",[37,6262,6263],{},"Loaders（JS以外のファイルを扱えるようにする）",[37,6265,6266],{},"Plugins（最適化したり、さらなる機能を追加する）",[37,6268,6269],{},"Mode（開発か本番か）",[20,6271,6272],{},"今のところ特にパッとしなくてもいいので、上記にあげた５パターンを再現するためにはこの概念が必要であること、そしてその通りに設定していることを頭に入れておいてください。",[27,6274,6276],{"id":6275},"まずは複数のjsファイルやライブラリをバンドルしてみよう","まずは複数のJSファイルやライブラリをバンドルしてみよう",[20,6278,6279],{},"まずはSassとかは忘れて、複数のJSファイルとモジュールの連携をやってみましょう。新しくディレクトリを作成してnpm initします。",[165,6281,6284],{"className":6282,"code":6283,"language":170},[168],"npm init\nnpm install -D webpack webpack-cli\n",[138,6285,6283],{"__ignoreMap":173},[20,6287,6288],{},"webpackは開発時しか使わないので -DをつけてdevDependenciesに入れます。インストール後にはnode_moduelsが作成されます。そして以下のディレクトリとファイルを作成します。",[165,6290,6293],{"className":6291,"code":6292,"language":170},[168],"├── dist #作成\n    ├── index.html #作成\n├── node_modules\n├── package-lock.json\n├── package.json\n├── webpack.config.js\n└── src　#作成\n    ├── functions.js　#作成\n    └── main.js　#作成\n",[138,6294,6292],{"__ignoreMap":173},[20,6296,6297,6298,159,6301,6304,6305,6307,6308,6311],{},"開発しているときは",[138,6299,6300],{},"main.js",[138,6302,6303],{},"functions.js","に記述していきます。",[138,6306,6303],{},"には共通の関数的な物を書き、モジュールとしてはおなじみの",[138,6309,6310],{},"jquery","をインストールしておきます。",[165,6313,6316],{"className":6314,"code":6315,"language":170},[168],"npm install jquery --save\n",[138,6317,6315],{"__ignoreMap":173},[20,6319,6320],{},"index.htmlを適当に以下のようにしておきます。",[165,6322,6325],{"className":977,"code":6323,"filename":6324,"language":980,"meta":173,"style":173},"\u003C!DOCTYPE html>\n\u003Chtml>\n    \u003Chead>\n        \u003Cmeta charset=\"utf-8\">\n        \u003Ctitle>webpackの練習\u003C\u002Ftitle>\n    \u003C\u002Fhead>\n\n    \u003Cbody>\n        \u003Cmain>\n            \u003Cdiv id='app'>\n\n            \u003C\u002Fdiv>\n        \u003C\u002Fmain>\n    \u003C\u002Fbody>\n    \u003Cscript src='.\u002Fbundle.js'>\u003C\u002Fscript>\n\u003C\u002Fhtml>\n","dist\u002Findex.html",[138,6326,6327,6337,6345,6353,6371,6388,6396,6400,6408,6417,6437,6441,6450,6459,6467,6490],{"__ignoreMap":173},[184,6328,6329,6331,6333,6335],{"class":186,"line":187},[184,6330,987],{"class":229},[184,6332,990],{"class":225},[184,6334,994],{"class":993},[184,6336,997],{"class":229},[184,6338,6339,6341,6343],{"class":186,"line":193},[184,6340,1002],{"class":229},[184,6342,980],{"class":225},[184,6344,997],{"class":229},[184,6346,6347,6349,6351],{"class":186,"line":251},[184,6348,2973],{"class":229},[184,6350,1017],{"class":225},[184,6352,997],{"class":229},[184,6354,6355,6357,6359,6361,6363,6365,6367,6369],{"class":186,"line":259},[184,6356,2987],{"class":229},[184,6358,1027],{"class":225},[184,6360,1030],{"class":993},[184,6362,1033],{"class":229},[184,6364,1036],{"class":229},[184,6366,1039],{"class":236},[184,6368,1036],{"class":229},[184,6370,997],{"class":229},[184,6372,6373,6375,6377,6379,6382,6384,6386],{"class":186,"line":271},[184,6374,2987],{"class":229},[184,6376,1050],{"class":225},[184,6378,1053],{"class":229},[184,6380,6381],{"class":1056},"webpackの練習",[184,6383,1060],{"class":229},[184,6385,1050],{"class":225},[184,6387,997],{"class":229},[184,6389,6390,6392,6394],{"class":186,"line":279},[184,6391,3017],{"class":229},[184,6393,1017],{"class":225},[184,6395,997],{"class":229},[184,6397,6398],{"class":186,"line":4},[184,6399,367],{"emptyLinePlaceholder":366},[184,6401,6402,6404,6406],{"class":186,"line":305},[184,6403,2973],{"class":229},[184,6405,1123],{"class":225},[184,6407,997],{"class":229},[184,6409,6410,6412,6415],{"class":186,"line":313},[184,6411,2987],{"class":229},[184,6413,6414],{"class":225},"main",[184,6416,997],{"class":229},[184,6418,6419,6422,6424,6426,6428,6430,6433,6435],{"class":186,"line":321},[184,6420,6421],{"class":229},"            \u003C",[184,6423,13],{"class":225},[184,6425,1170],{"class":993},[184,6427,1033],{"class":229},[184,6429,1260],{"class":229},[184,6431,6432],{"class":236},"app",[184,6434,1260],{"class":229},[184,6436,997],{"class":229},[184,6438,6439],{"class":186,"line":329},[184,6440,367],{"emptyLinePlaceholder":366},[184,6442,6443,6446,6448],{"class":186,"line":412},[184,6444,6445],{"class":229},"            \u003C\u002F",[184,6447,13],{"class":225},[184,6449,997],{"class":229},[184,6451,6452,6455,6457],{"class":186,"line":417},[184,6453,6454],{"class":229},"        \u003C\u002F",[184,6456,6414],{"class":225},[184,6458,997],{"class":229},[184,6460,6461,6463,6465],{"class":186,"line":423},[184,6462,3017],{"class":229},[184,6464,1123],{"class":225},[184,6466,997],{"class":229},[184,6468,6469,6471,6473,6475,6477,6479,6482,6484,6486,6488],{"class":186,"line":429},[184,6470,2973],{"class":229},[184,6472,1137],{"class":225},[184,6474,1140],{"class":993},[184,6476,1033],{"class":229},[184,6478,1260],{"class":229},[184,6480,6481],{"class":236},".\u002Fbundle.js",[184,6483,1260],{"class":229},[184,6485,1152],{"class":229},[184,6487,1137],{"class":225},[184,6489,997],{"class":229},[184,6491,6492,6494,6496],{"class":186,"line":435},[184,6493,1060],{"class":229},[184,6495,980],{"class":225},[184,6497,997],{"class":229},[20,6499,6500],{},"それでmain.jsは適当にこうしておきましょう。",[165,6502,6507],{"className":6503,"code":6504,"filename":6505,"language":6506,"meta":173,"style":173},"language-javascript shiki shiki-themes material-theme-ocean","import $ from 'jquery';\n\n$('#app').text('hello world')\n","src\u002Fmain.js","javascript",[138,6508,6509,6526,6530],{"__ignoreMap":173},[184,6510,6511,6513,6516,6518,6520,6522,6524],{"class":186,"line":187},[184,6512,2819],{"class":2759},[184,6514,6515],{"class":1056}," $ ",[184,6517,2825],{"class":2759},[184,6519,233],{"class":229},[184,6521,6310],{"class":236},[184,6523,1260],{"class":229},[184,6525,1321],{"class":229},[184,6527,6528],{"class":186,"line":193},[184,6529,367],{"emptyLinePlaceholder":366},[184,6531,6532,6535,6537,6539,6541,6543,6545,6547,6549,6551,6553,6556,6558],{"class":186,"line":251},[184,6533,6534],{"class":1253},"$",[184,6536,1257],{"class":1056},[184,6538,1260],{"class":229},[184,6540,3482],{"class":236},[184,6542,1260],{"class":229},[184,6544,1294],{"class":1056},[184,6546,1304],{"class":229},[184,6548,170],{"class":1253},[184,6550,1257],{"class":1056},[184,6552,1260],{"class":229},[184,6554,6555],{"class":236},"hello world",[184,6557,1260],{"class":229},[184,6559,3426],{"class":1056},[20,6561,6562,6565,6566,6569,6570,6572,6573,6575],{},[138,6563,6564],{},"node_modules"," からjQueryをインポートして、",[138,6567,6568],{},"index.html","を操作します。しかし今のままではバンドルされていないので、main.jsの内容は利用できません。",[138,6571,3519],{},"を設定して、",[138,6574,6300],{},"をビルドしてみましょう。",[20,6577,6578,6580],{},[138,6579,3519],{},"を以下のように設定してみましょう。",[165,6582,6584],{"className":6503,"code":6583,"filename":3519,"language":6506,"meta":173,"style":173},"module.exports = {\n    \u002F\u002Fバンドル対象のファイル\n    entry: `.\u002Fsrc\u002Fmain.js`,\n  \n    mode:\"development\",\n    \u002F\u002F ファイルの出力設定\n    output: {\n      \u002F\u002F  出力ファイルのディレクトリ名\n      path: `${__dirname}\u002Fdist`,\n      \u002F\u002F 出力ファイル名\n      filename: \"bundle.js\"\n    }\n};\n",[138,6585,6586,6595,6600,6618,6623,6639,6644,6653,6658,6679,6684,6698,6702],{"__ignoreMap":173},[184,6587,6588,6591,6593],{"class":186,"line":187},[184,6589,6590],{"class":229},"module.exports",[184,6592,6002],{"class":229},[184,6594,1270],{"class":229},[184,6596,6597],{"class":186,"line":193},[184,6598,6599],{"class":1069},"    \u002F\u002Fバンドル対象のファイル\n",[184,6601,6602,6605,6607,6610,6613,6616],{"class":186,"line":251},[184,6603,6604],{"class":225},"    entry",[184,6606,230],{"class":229},[184,6608,6609],{"class":229}," `",[184,6611,6612],{"class":236},".\u002Fsrc\u002Fmain.js",[184,6614,6615],{"class":229},"`",[184,6617,3457],{"class":229},[184,6619,6620],{"class":186,"line":259},[184,6621,6622],{"class":1056},"  \n",[184,6624,6625,6628,6630,6632,6635,6637],{"class":186,"line":271},[184,6626,6627],{"class":225},"    mode",[184,6629,230],{"class":229},[184,6631,1036],{"class":229},[184,6633,6634],{"class":236},"development",[184,6636,1036],{"class":229},[184,6638,3457],{"class":229},[184,6640,6641],{"class":186,"line":279},[184,6642,6643],{"class":1069},"    \u002F\u002F ファイルの出力設定\n",[184,6645,6646,6649,6651],{"class":186,"line":4},[184,6647,6648],{"class":225},"    output",[184,6650,230],{"class":229},[184,6652,1270],{"class":229},[184,6654,6655],{"class":186,"line":305},[184,6656,6657],{"class":1069},"      \u002F\u002F  出力ファイルのディレクトリ名\n",[184,6659,6660,6663,6665,6668,6671,6673,6675,6677],{"class":186,"line":313},[184,6661,6662],{"class":225},"      path",[184,6664,230],{"class":229},[184,6666,6667],{"class":229}," `${",[184,6669,6670],{"class":1056},"__dirname",[184,6672,3423],{"class":229},[184,6674,5667],{"class":236},[184,6676,6615],{"class":229},[184,6678,3457],{"class":229},[184,6680,6681],{"class":186,"line":321},[184,6682,6683],{"class":1069},"      \u002F\u002F 出力ファイル名\n",[184,6685,6686,6689,6691,6693,6696],{"class":186,"line":329},[184,6687,6688],{"class":225},"      filename",[184,6690,230],{"class":229},[184,6692,285],{"class":229},[184,6694,6695],{"class":236},"bundle.js",[184,6697,291],{"class":229},[184,6699,6700],{"class":186,"line":412},[184,6701,520],{"class":229},[184,6703,6704],{"class":186,"line":417},[184,6705,6706],{"class":229},"};\n",[20,6708,6709,6710,6712,6713,6715,6716,6719,6720,6722],{},"ここで先ほどの５つの概念を思い出してください。Entry、Output、Modeの設定が書かれています。パスは",[138,6711,3519],{},"から見たパスになります。エントリーはsrc配下の",[138,6714,6300],{},"、吐き出し先は同じ階層の",[138,6717,6718],{},"dist","に",[138,6721,6695],{},"という名前で吐き出します。",[20,6724,6725,2708,6727,6730],{},[138,6726,6590],{},[138,6728,6729],{},"node.js","のモジュールとして利用するために必要です。webpackはnode.jsの環境下でビルドを行うからです。",[20,6732,6733,6734,6736],{},"設定が終わったら",[138,6735,4429],{},"に以下の内容を付け足しておきます。",[165,6738,6742],{"className":6739,"code":6740,"filename":4429,"language":6741,"meta":173,"style":173},"language-json shiki shiki-themes material-theme-ocean","\"scripts\": {\n    \"build\": \"npx webpack-cli build\", #これ\n    \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n},\n","json",[138,6743,6744,6758,6782,6811],{"__ignoreMap":173},[184,6745,6746,6748,6751,6753,6756],{"class":186,"line":187},[184,6747,1036],{"class":229},[184,6749,6750],{"class":236},"scripts",[184,6752,1036],{"class":229},[184,6754,6755],{"class":1056},": ",[184,6757,3449],{"class":229},[184,6759,6760,6763,6766,6768,6770,6772,6775,6777,6779],{"class":186,"line":193},[184,6761,6762],{"class":229},"    \"",[184,6764,6765],{"class":993},"build",[184,6767,1036],{"class":229},[184,6769,230],{"class":229},[184,6771,285],{"class":229},[184,6773,6774],{"class":236},"npx webpack-cli build",[184,6776,1036],{"class":229},[184,6778,1267],{"class":229},[184,6780,6781],{"class":1056}," #これ\n",[184,6783,6784,6786,6789,6791,6793,6795,6798,6801,6804,6806,6809],{"class":186,"line":251},[184,6785,6762],{"class":229},[184,6787,6788],{"class":993},"test",[184,6790,1036],{"class":229},[184,6792,230],{"class":229},[184,6794,285],{"class":229},[184,6796,6797],{"class":236},"echo ",[184,6799,6800],{"class":1056},"\\\"",[184,6802,6803],{"class":236},"Error: no test specified",[184,6805,6800],{"class":1056},[184,6807,6808],{"class":236}," && exit 1",[184,6810,291],{"class":229},[184,6812,6813,6815],{"class":186,"line":259},[184,6814,3423],{"class":229},[184,6816,3457],{"class":1056},[20,6818,6819,6822,6823,6826],{},[138,6820,6821],{},"npm run build","を叩くと",[138,6824,6825],{},"webpack","のバンドルが走ります。それではやってみましょう。",[165,6828,6831],{"className":6829,"code":6830,"language":170},[168],"npm run build\n> webpack_parctice@1.0.0 build \u002FUsers\u002Fjunjiishii\u002FDesktop\u002Fmy_apps\u002Fwebpack_parctice\n> webpack\n\nasset bundle.js 323 KiB [emitted] (name: main)\nruntime modules 937 bytes 4 modules\ncacheable modules 282 KiB\n  .\u002Fsrc\u002Fmain.js 54 bytes [built] [code generated]\n  .\u002Fnode_modules\u002Fjquery\u002Fdist\u002Fjquery.js 282 KiB [built] [code generated]\nwebpack 5.28.0 compiled successfully in 234 ms\n",[138,6832,6830],{"__ignoreMap":173},[20,6834,6835,6836,6838,6839,6841],{},"jqueryがきちんとnode_modulesから読み込まれいます。そして",[138,6837,6718],{},"配下を見るとbundle.jsができています。",[138,6840,6695],{},"の中身をみてみますと、",[87,6843],{":src":6844,":width":6845,":center":1446},"'_mix\u002Fsch-2021-03-27-17.29.11.png'","'500px'",[20,6847,6848],{},"わお。必要なjsファイルが一つにまとめられているので、こうなっています。main.js一行とjqueryがくっついています。それではindex.htmlをみてみると..",[87,6850],{":src":6844,":width":6845,":center":1446},[20,6852,6853,6854,6856,6857,159,6859,6861],{},"はい。",[138,6855,6300],{},"でhello worldを入れたのできちんと",[138,6858,6300],{},[138,6860,6310],{},"がバンドルされ、動作していることが確認されました。",[27,6863,6864],{"id":6864},"自前のファイルをバンドルしよう",[20,6866,6867,6868,5154,6870,6872,6873,6875,6876,5412,6878,5412,6880,6882],{},"先程は",[138,6869,6564],{},[138,6871,6310],{},"をインポートして利用してみました。次は",[138,6874,6303],{},"の関数群をインポートして利用できるようにしてみましょう。",[138,6877,6568],{},[138,6879,6300],{},[138,6881,6303],{},"を以下のように変更します。",[165,6884,6886],{"className":977,"code":6885,"filename":6324,"language":980,"meta":173,"style":173},"\u003C!-- body のみ見せます -->\n\u003Cbody>\n    \u003Cmain>\n        \u003Cdiv id='app'>\u003C\u002Fdiv>\n        \u003Cinput type=\"text\" value=\"\" id=\"inputs\">\n        \u003Cbutton id=\"submmit\" >追加する\u003C\u002Fbutton>\n    \u003C\u002Fmain>\n\u003C\u002Fbody>\n",[138,6887,6888,6893,6901,6909,6931,6969,6999,7007],{"__ignoreMap":173},[184,6889,6890],{"class":186,"line":187},[184,6891,6892],{"class":1069},"\u003C!-- body のみ見せます -->\n",[184,6894,6895,6897,6899],{"class":186,"line":193},[184,6896,1002],{"class":229},[184,6898,1123],{"class":225},[184,6900,997],{"class":229},[184,6902,6903,6905,6907],{"class":186,"line":251},[184,6904,2973],{"class":229},[184,6906,6414],{"class":225},[184,6908,997],{"class":229},[184,6910,6911,6913,6915,6917,6919,6921,6923,6925,6927,6929],{"class":186,"line":259},[184,6912,2987],{"class":229},[184,6914,13],{"class":225},[184,6916,1170],{"class":993},[184,6918,1033],{"class":229},[184,6920,1260],{"class":229},[184,6922,6432],{"class":236},[184,6924,1260],{"class":229},[184,6926,1152],{"class":229},[184,6928,13],{"class":225},[184,6930,997],{"class":229},[184,6932,6933,6935,6938,6940,6942,6944,6946,6948,6951,6953,6956,6958,6960,6962,6965,6967],{"class":186,"line":271},[184,6934,2987],{"class":229},[184,6936,6937],{"class":225},"input",[184,6939,1222],{"class":993},[184,6941,1033],{"class":229},[184,6943,1036],{"class":229},[184,6945,170],{"class":236},[184,6947,1036],{"class":229},[184,6949,6950],{"class":993}," value",[184,6952,1033],{"class":229},[184,6954,6955],{"class":229},"\"\"",[184,6957,1170],{"class":993},[184,6959,1033],{"class":229},[184,6961,1036],{"class":229},[184,6963,6964],{"class":236},"inputs",[184,6966,1036],{"class":229},[184,6968,997],{"class":229},[184,6970,6971,6973,6976,6978,6980,6982,6985,6987,6990,6993,6995,6997],{"class":186,"line":279},[184,6972,2987],{"class":229},[184,6974,6975],{"class":225},"button",[184,6977,1170],{"class":993},[184,6979,1033],{"class":229},[184,6981,1036],{"class":229},[184,6983,6984],{"class":236},"submmit",[184,6986,1036],{"class":229},[184,6988,6989],{"class":229}," >",[184,6991,6992],{"class":1056},"追加する",[184,6994,1060],{"class":229},[184,6996,6975],{"class":225},[184,6998,997],{"class":229},[184,7000,7001,7003,7005],{"class":186,"line":4},[184,7002,3017],{"class":229},[184,7004,6414],{"class":225},[184,7006,997],{"class":229},[184,7008,7009,7011,7013],{"class":186,"line":305},[184,7010,1060],{"class":229},[184,7012,1123],{"class":225},[184,7014,997],{"class":229},[165,7016,7018],{"className":6503,"code":7017,"filename":6303,"language":6506,"meta":173,"style":173},"import $ from 'jquery';\n\nexport default {\n    addNewText(to,input){\n        let text = $(input).val();\n        $(to).append('\u003Cp>'+text+'\u003C\u002Fp>');　#XSSできちゃうので本番では使わないように。。\n        $(input).val('');\n    }\n}\n",[138,7019,7020,7036,7040,7048,7065,7093,7146,7169,7173],{"__ignoreMap":173},[184,7021,7022,7024,7026,7028,7030,7032,7034],{"class":186,"line":187},[184,7023,2819],{"class":2759},[184,7025,6515],{"class":1056},[184,7027,2825],{"class":2759},[184,7029,233],{"class":229},[184,7031,6310],{"class":236},[184,7033,1260],{"class":229},[184,7035,1321],{"class":229},[184,7037,7038],{"class":186,"line":193},[184,7039,367],{"emptyLinePlaceholder":366},[184,7041,7042,7044,7046],{"class":186,"line":251},[184,7043,2760],{"class":2759},[184,7045,2763],{"class":2759},[184,7047,1270],{"class":229},[184,7049,7050,7053,7055,7058,7060,7062],{"class":186,"line":259},[184,7051,7052],{"class":225},"    addNewText",[184,7054,1257],{"class":229},[184,7056,7057],{"class":1285},"to",[184,7059,1267],{"class":229},[184,7061,6937],{"class":1285},[184,7063,7064],{"class":229},"){\n",[184,7066,7067,7070,7073,7075,7078,7080,7082,7084,7086,7089,7091],{"class":186,"line":271},[184,7068,7069],{"class":993},"        let",[184,7071,7072],{"class":1056}," text",[184,7074,6002],{"class":229},[184,7076,7077],{"class":1253}," $",[184,7079,1257],{"class":225},[184,7081,6937],{"class":1056},[184,7083,1294],{"class":225},[184,7085,1304],{"class":229},[184,7087,7088],{"class":1253},"val",[184,7090,3696],{"class":225},[184,7092,1321],{"class":229},[184,7094,7095,7098,7100,7102,7104,7106,7109,7111,7113,7116,7118,7121,7123,7125,7127,7130,7132,7134,7137,7140,7143],{"class":186,"line":279},[184,7096,7097],{"class":1253},"        $",[184,7099,1257],{"class":225},[184,7101,7057],{"class":1056},[184,7103,1294],{"class":225},[184,7105,1304],{"class":229},[184,7107,7108],{"class":1253},"append",[184,7110,1257],{"class":225},[184,7112,1260],{"class":229},[184,7114,7115],{"class":236},"\u003Cp>",[184,7117,1260],{"class":229},[184,7119,7120],{"class":229},"+",[184,7122,170],{"class":1056},[184,7124,7120],{"class":229},[184,7126,1260],{"class":229},[184,7128,7129],{"class":236},"\u003C\u002Fp>",[184,7131,1260],{"class":229},[184,7133,1294],{"class":225},[184,7135,7136],{"class":229},";",[184,7138,7139],{"class":225},"　#",[184,7141,7142],{"class":1056},"XSSできちゃうので本番では使わないように",[184,7144,7145],{"class":225},"。。\n",[184,7147,7148,7150,7152,7154,7156,7158,7160,7162,7165,7167],{"class":186,"line":4},[184,7149,7097],{"class":1253},[184,7151,1257],{"class":225},[184,7153,6937],{"class":1056},[184,7155,1294],{"class":225},[184,7157,1304],{"class":229},[184,7159,7088],{"class":1253},[184,7161,1257],{"class":225},[184,7163,7164],{"class":229},"''",[184,7166,1294],{"class":225},[184,7168,1321],{"class":229},[184,7170,7171],{"class":186,"line":305},[184,7172,520],{"class":229},[184,7174,7175],{"class":186,"line":313},[184,7176,400],{"class":229},[165,7178,7180],{"className":6503,"code":7179,"filename":6505,"language":6506,"meta":173,"style":173},"import $ from 'jquery';\nimport funcs from '.\u002Ffunctions';\n\n$('#submmit').on('click',()=>{\n    return funcs.addNewText('#app','#inputs');\n})\n\n",[138,7181,7182,7198,7216,7220,7257,7291],{"__ignoreMap":173},[184,7183,7184,7186,7188,7190,7192,7194,7196],{"class":186,"line":187},[184,7185,2819],{"class":2759},[184,7187,6515],{"class":1056},[184,7189,2825],{"class":2759},[184,7191,233],{"class":229},[184,7193,6310],{"class":236},[184,7195,1260],{"class":229},[184,7197,1321],{"class":229},[184,7199,7200,7202,7205,7207,7209,7212,7214],{"class":186,"line":193},[184,7201,2819],{"class":2759},[184,7203,7204],{"class":1056}," funcs ",[184,7206,2825],{"class":2759},[184,7208,233],{"class":229},[184,7210,7211],{"class":236},".\u002Ffunctions",[184,7213,1260],{"class":229},[184,7215,1321],{"class":229},[184,7217,7218],{"class":186,"line":251},[184,7219,367],{"emptyLinePlaceholder":366},[184,7221,7222,7224,7226,7228,7231,7233,7235,7237,7240,7242,7244,7247,7249,7252,7255],{"class":186,"line":259},[184,7223,6534],{"class":1253},[184,7225,1257],{"class":1056},[184,7227,1260],{"class":229},[184,7229,7230],{"class":236},"#submmit",[184,7232,1260],{"class":229},[184,7234,1294],{"class":1056},[184,7236,1304],{"class":229},[184,7238,7239],{"class":1253},"on",[184,7241,1257],{"class":1056},[184,7243,1260],{"class":229},[184,7245,7246],{"class":236},"click",[184,7248,1260],{"class":229},[184,7250,7251],{"class":229},",()",[184,7253,7254],{"class":993},"=>",[184,7256,3449],{"class":229},[184,7258,7259,7262,7265,7267,7270,7272,7274,7276,7278,7280,7282,7285,7287,7289],{"class":186,"line":271},[184,7260,7261],{"class":2759},"    return",[184,7263,7264],{"class":1056}," funcs",[184,7266,1304],{"class":229},[184,7268,7269],{"class":1253},"addNewText",[184,7271,1257],{"class":225},[184,7273,1260],{"class":229},[184,7275,3482],{"class":236},[184,7277,1260],{"class":229},[184,7279,1267],{"class":229},[184,7281,1260],{"class":229},[184,7283,7284],{"class":236},"#inputs",[184,7286,1260],{"class":229},[184,7288,1294],{"class":225},[184,7290,1321],{"class":229},[184,7292,7293,7295],{"class":186,"line":279},[184,7294,3423],{"class":229},[184,7296,3426],{"class":1056},[20,7298,7299,7300,7303],{},"functions.jsには便利関数を入れているのを想定しているので、オブジェクトに関数を入れておきます。それをexportします。",[138,7301,7302],{},"addNewText()","は指定した入力フォームの文字列を、指定したDOMにpタグとして入れてくれる神メソッドです。TODO LIST的な物を開発していると思ってください。",[20,7305,7306,1487,7308,7311,7312,7315,7316,7319],{},[138,7307,6300],{},[138,7309,7310],{},"import funcs from '.\u002Ffunctions';","で読み込みます。",[138,7313,7314],{},"funcs","というのはオブジェクトなので",[138,7317,7318],{},"funcs.addNewText()","で使用できます。イベントリスナーでボタンを押したら追加できるようにしてみましょう。",[20,7321,7322,7323,7325],{},"ビルドしたら再度",[138,7324,6568],{},"をみてみます。",[87,7327],{":src":7328,":width":7329},"'_mix\u002Fsch-2021-03-27-18.04.19-768x283.png'","'300px'",[87,7331],{":src":7332,":width":7329},"'_mix\u002Fsch-2021-03-27-18.05.16.png'",[20,7334,7335],{},"入力内容を入れて、追加するを押すとこのように文字が追加されました。webpackが各ファイルのimportの関係性を解決してくれるので、これで複数ファイルのバンドルができるようになりました。",[47,7337,7338],{"id":7338},"watchモードを追加しておく",[20,7340,7341,7342,7344],{},"jsのコードを書いてビルドしたらスクリプトにエラーが起きていたという時、毎回ビルドするのは面倒です。webpackにはwatchモードという物があり、変更を検知して差分ビルドをしてくれます。",[138,7343,4429],{},"に以下のように記述します。",[165,7346,7348],{"className":6739,"code":7347,"filename":4429,"language":6741,"meta":173,"style":173},"\"scripts\": {\n  \"build\": \"npx webpack-cli build\",\n  \"watch\": \"npx webpack-cli watch\",　\b#これ\n  \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n},\n",[138,7349,7350,7362,7381,7404,7428],{"__ignoreMap":173},[184,7351,7352,7354,7356,7358,7360],{"class":186,"line":187},[184,7353,1036],{"class":229},[184,7355,6750],{"class":236},[184,7357,1036],{"class":229},[184,7359,6755],{"class":1056},[184,7361,3449],{"class":229},[184,7363,7364,7367,7369,7371,7373,7375,7377,7379],{"class":186,"line":193},[184,7365,7366],{"class":229},"  \"",[184,7368,6765],{"class":993},[184,7370,1036],{"class":229},[184,7372,230],{"class":229},[184,7374,285],{"class":229},[184,7376,6774],{"class":236},[184,7378,1036],{"class":229},[184,7380,3457],{"class":229},[184,7382,7383,7385,7388,7390,7392,7394,7397,7399,7401],{"class":186,"line":251},[184,7384,7366],{"class":229},[184,7386,7387],{"class":993},"watch",[184,7389,1036],{"class":229},[184,7391,230],{"class":229},[184,7393,285],{"class":229},[184,7395,7396],{"class":236},"npx webpack-cli watch",[184,7398,1036],{"class":229},[184,7400,1267],{"class":229},[184,7402,7403],{"class":1056},"　\b#これ\n",[184,7405,7406,7408,7410,7412,7414,7416,7418,7420,7422,7424,7426],{"class":186,"line":259},[184,7407,7366],{"class":229},[184,7409,6788],{"class":993},[184,7411,1036],{"class":229},[184,7413,230],{"class":229},[184,7415,285],{"class":229},[184,7417,6797],{"class":236},[184,7419,6800],{"class":1056},[184,7421,6803],{"class":236},[184,7423,6800],{"class":1056},[184,7425,6808],{"class":236},[184,7427,291],{"class":229},[184,7429,7430,7432],{"class":186,"line":271},[184,7431,3423],{"class":229},[184,7433,3457],{"class":1056},[20,7435,2432,7436,7439,7440,7442],{},[138,7437,7438],{},"npm run watch"," を行うことで",[138,7441,7387],{},"モードでビルドが動くようになります。",[27,7444,7446],{"id":7445},"sassをコンパイルバンドルしてみる","Sassをコンパイル・バンドルしてみる",[20,7448,7449],{},"Webpackは基本的にjsファイルのバンドルを想定していますが、css・sassもバンドルできます。今回はsassの説明をします。",[47,7451,7453],{"id":7452},"必要なモジュールを追加インストール設定","必要なモジュールを追加インストール&設定",[20,7455,7456],{},"webpackがsassをバンドルしてビルドするには",[34,7458,7459,7462,7465,7468],{},[37,7460,7461],{},"sass-loader",[37,7463,7464],{},"node-sass",[37,7466,7467],{},"css-loader",[37,7469,7470],{},"mini-css-extract-plugin",[20,7472,7473],{},"の４つのdevDependenciesが必要になります。",[20,7475,7476,7478,7479,7481,7482,7485],{},[68,7477,7461],{},"は重要な概念に出てきた「loader」に該当します。webpackがsassファイルをバンドルするのに必要です。",[68,7480,7464],{},"はnode.jsでsassのコンパイルをするために必要です。sassをコンパイルして生成されたcssを扱うために",[68,7483,7484],{},"css-loader、mini-css-extract-plugin","が必要です。そのためまずはインストールしてみましょう。",[165,7487,7490],{"className":7488,"code":7489,"language":170},[168],"npm install -D sass-loader node-sass css-loader mini-css-extract-plugin\n",[138,7491,7489],{"__ignoreMap":173},[20,7493,7494],{},"ひとまず入れられたら、webpack.config.jsを以下のように変更します。",[165,7496,7498],{"className":6503,"code":7497,"filename":3519,"language":6506,"meta":173,"style":173},"const MiniCssExtractPlugin = require('mini-css-extract-plugin');\n\nmodule.exports = {\n    \u002F\u002Fバンドル対象のファイル\n    entry: '.\u002Fsrc\u002Fjs\u002Fmain.js',\n  \n    mode:\"development\",\n    \n    \u002F\u002F ここから追加\n    module: {\n        rules: [\n          {\n            test: \u002F\\.(sa|sc|c)ss$\u002F,\n            exclude: \u002Fnode_modules\u002F,\n            use: [\n              MiniCssExtractPlugin.loader,\n              {\n                loader: 'css-loader',\n                options: { url: false }\n              },\n              'sass-loader'\n            ]\n          }\n        ]\n      },\n    \u002F\u002F ファイルの出力設定\n    output: {\n      \u002F\u002F  出力ファイルのディレクトリ名\n      path: `${__dirname}\u002Fdist`,\n      \u002F\u002F 出力ファイル名\n      filename: \"bundle.js\"\n    },\n 　　\u002F\u002F ここも追加\n    plugins: [\n      new MiniCssExtractPlugin({\n          filename:'style.css'\n      })\n    ]\n};\n",[138,7499,7500,7523,7527,7535,7539,7554,7558,7572,7577,7582,7591,7600,7605,7646,7661,7670,7682,7687,7702,7723,7728,7737,7742,7747,7752,7757,7761,7769,7773,7791,7795,7807,7811,7816,7825,7837,7851,7857,7862],{"__ignoreMap":173},[184,7501,7502,7504,7507,7509,7511,7513,7515,7517,7519,7521],{"class":186,"line":187},[184,7503,2861],{"class":993},[184,7505,7506],{"class":1056}," MiniCssExtractPlugin ",[184,7508,1033],{"class":229},[184,7510,3178],{"class":1253},[184,7512,1257],{"class":1056},[184,7514,1260],{"class":229},[184,7516,7470],{"class":236},[184,7518,1260],{"class":229},[184,7520,1294],{"class":1056},[184,7522,1321],{"class":229},[184,7524,7525],{"class":186,"line":193},[184,7526,367],{"emptyLinePlaceholder":366},[184,7528,7529,7531,7533],{"class":186,"line":251},[184,7530,6590],{"class":229},[184,7532,6002],{"class":229},[184,7534,1270],{"class":229},[184,7536,7537],{"class":186,"line":259},[184,7538,6599],{"class":1069},[184,7540,7541,7543,7545,7547,7550,7552],{"class":186,"line":271},[184,7542,6604],{"class":225},[184,7544,230],{"class":229},[184,7546,233],{"class":229},[184,7548,7549],{"class":236},".\u002Fsrc\u002Fjs\u002Fmain.js",[184,7551,1260],{"class":229},[184,7553,3457],{"class":229},[184,7555,7556],{"class":186,"line":279},[184,7557,6622],{"class":1056},[184,7559,7560,7562,7564,7566,7568,7570],{"class":186,"line":4},[184,7561,6627],{"class":225},[184,7563,230],{"class":229},[184,7565,1036],{"class":229},[184,7567,6634],{"class":236},[184,7569,1036],{"class":229},[184,7571,3457],{"class":229},[184,7573,7574],{"class":186,"line":305},[184,7575,7576],{"class":1056},"    \n",[184,7578,7579],{"class":186,"line":313},[184,7580,7581],{"class":1069},"    \u002F\u002F ここから追加\n",[184,7583,7584,7587,7589],{"class":186,"line":321},[184,7585,7586],{"class":225},"    module",[184,7588,230],{"class":229},[184,7590,1270],{"class":229},[184,7592,7593,7596,7598],{"class":186,"line":329},[184,7594,7595],{"class":225},"        rules",[184,7597,230],{"class":229},[184,7599,2869],{"class":1056},[184,7601,7602],{"class":186,"line":412},[184,7603,7604],{"class":229},"          {\n",[184,7606,7607,7610,7612,7615,7618,7620,7623,7626,7629,7631,7634,7636,7639,7641,7644],{"class":186,"line":417},[184,7608,7609],{"class":225},"            test",[184,7611,230],{"class":229},[184,7613,7614],{"class":229}," \u002F",[184,7616,7617],{"class":1056},"\\.",[184,7619,1257],{"class":229},[184,7621,7622],{"class":236},"sa",[184,7624,7625],{"class":229},"|",[184,7627,7628],{"class":236},"sc",[184,7630,7625],{"class":229},[184,7632,7633],{"class":236},"c",[184,7635,1294],{"class":229},[184,7637,7638],{"class":236},"ss",[184,7640,6534],{"class":2759},[184,7642,7643],{"class":229},"\u002F",[184,7645,3457],{"class":229},[184,7647,7648,7651,7653,7655,7657,7659],{"class":186,"line":423},[184,7649,7650],{"class":225},"            exclude",[184,7652,230],{"class":229},[184,7654,7614],{"class":229},[184,7656,6564],{"class":236},[184,7658,7643],{"class":229},[184,7660,3457],{"class":229},[184,7662,7663,7666,7668],{"class":186,"line":429},[184,7664,7665],{"class":225},"            use",[184,7667,230],{"class":229},[184,7669,2869],{"class":1056},[184,7671,7672,7675,7677,7680],{"class":186,"line":435},[184,7673,7674],{"class":1056},"              MiniCssExtractPlugin",[184,7676,1304],{"class":229},[184,7678,7679],{"class":1056},"loader",[184,7681,3457],{"class":229},[184,7683,7684],{"class":186,"line":441},[184,7685,7686],{"class":229},"              {\n",[184,7688,7689,7692,7694,7696,7698,7700],{"class":186,"line":447},[184,7690,7691],{"class":225},"                loader",[184,7693,230],{"class":229},[184,7695,233],{"class":229},[184,7697,7467],{"class":236},[184,7699,1260],{"class":229},[184,7701,3457],{"class":229},[184,7703,7704,7707,7709,7712,7715,7717,7720],{"class":186,"line":453},[184,7705,7706],{"class":225},"                options",[184,7708,230],{"class":229},[184,7710,7711],{"class":229}," {",[184,7713,7714],{"class":225}," url",[184,7716,230],{"class":229},[184,7718,7719],{"class":5557}," false",[184,7721,7722],{"class":229}," }\n",[184,7724,7725],{"class":186,"line":459},[184,7726,7727],{"class":229},"              },\n",[184,7729,7730,7733,7735],{"class":186,"line":464},[184,7731,7732],{"class":229},"              '",[184,7734,7461],{"class":236},[184,7736,240],{"class":229},[184,7738,7739],{"class":186,"line":470},[184,7740,7741],{"class":1056},"            ]\n",[184,7743,7744],{"class":186,"line":476},[184,7745,7746],{"class":229},"          }\n",[184,7748,7749],{"class":186,"line":481},[184,7750,7751],{"class":1056},"        ]\n",[184,7753,7754],{"class":186,"line":487},[184,7755,7756],{"class":229},"      },\n",[184,7758,7759],{"class":186,"line":493},[184,7760,6643],{"class":1069},[184,7762,7763,7765,7767],{"class":186,"line":499},[184,7764,6648],{"class":225},[184,7766,230],{"class":229},[184,7768,1270],{"class":229},[184,7770,7771],{"class":186,"line":505},[184,7772,6657],{"class":1069},[184,7774,7775,7777,7779,7781,7783,7785,7787,7789],{"class":186,"line":511},[184,7776,6662],{"class":225},[184,7778,230],{"class":229},[184,7780,6667],{"class":229},[184,7782,6670],{"class":1056},[184,7784,3423],{"class":229},[184,7786,5667],{"class":236},[184,7788,6615],{"class":229},[184,7790,3457],{"class":229},[184,7792,7793],{"class":186,"line":517},[184,7794,6683],{"class":1069},[184,7796,7797,7799,7801,7803,7805],{"class":186,"line":523},[184,7798,6688],{"class":225},[184,7800,230],{"class":229},[184,7802,285],{"class":229},[184,7804,6695],{"class":236},[184,7806,291],{"class":229},[184,7808,7809],{"class":186,"line":528},[184,7810,6054],{"class":229},[184,7812,7813],{"class":186,"line":533},[184,7814,7815],{"class":1069}," 　　\u002F\u002F ここも追加\n",[184,7817,7818,7821,7823],{"class":186,"line":538},[184,7819,7820],{"class":225},"    plugins",[184,7822,230],{"class":229},[184,7824,2869],{"class":1056},[184,7826,7827,7830,7833,7835],{"class":186,"line":543},[184,7828,7829],{"class":229},"      new",[184,7831,7832],{"class":1253}," MiniCssExtractPlugin",[184,7834,1257],{"class":1056},[184,7836,3449],{"class":229},[184,7838,7839,7842,7844,7846,7849],{"class":186,"line":549},[184,7840,7841],{"class":225},"          filename",[184,7843,230],{"class":229},[184,7845,1260],{"class":229},[184,7847,7848],{"class":236},"style.css",[184,7850,240],{"class":229},[184,7852,7853,7855],{"class":186,"line":555},[184,7854,1330],{"class":229},[184,7856,3426],{"class":1056},[184,7858,7859],{"class":186,"line":561},[184,7860,7861],{"class":1056},"    ]\n",[184,7863,7864],{"class":186,"line":566},[184,7865,6706],{"class":229},[47,7867,7869],{"id":7868},"アセットのディレクトリとindexhtmlの変更","アセットのディレクトリとindex.htmlの変更",[20,7871,7872],{},"srcにsassのディレクトリを作成し、そしてややこしいのでjsのフォルダも作りました。",[165,7874,7877],{"className":7875,"code":7876,"language":170},[168],".\n├── dist\n│   ├── index.html\n├── package-lock.json\n├── package.json\n├── src\n│   ├── js\n│   │   ├── functions.js\n│   │   └── main.js\n│   └── sass\n│       ├── component.scss\n│       ├── style.scss\n│       └── variable.scss\n└── webpack.config.js\n",[138,7878,7876],{"__ignoreMap":173},[20,7880,7881],{},"sassの３ファイルは以下のような構成になっています。",[165,7883,7888],{"className":7884,"code":7885,"filename":7886,"language":7887,"meta":173,"style":173},"language-scss shiki shiki-themes material-theme-ocean","$base_color:red;\n$box_size:30px;\n","variacle.scss","scss",[138,7889,7890,7902],{"__ignoreMap":173},[184,7891,7892,7895,7897,7900],{"class":186,"line":187},[184,7893,7894],{"class":1056},"$base_color",[184,7896,230],{"class":229},[184,7898,7899],{"class":1056},"red",[184,7901,1321],{"class":229},[184,7903,7904,7907,7909,7912],{"class":186,"line":193},[184,7905,7906],{"class":1056},"$box_size",[184,7908,230],{"class":229},[184,7910,7911],{"class":267},"30px",[184,7913,1321],{"class":229},[165,7915,7918],{"className":7884,"code":7916,"filename":7917,"language":7887,"meta":173,"style":173},".box{\n    background-color: $base_color;\n    width: 20px;\n    height: 20px;\n}\n","component.scss",[138,7919,7920,7929,7942,7954,7965],{"__ignoreMap":173},[184,7921,7922,7924,7927],{"class":186,"line":187},[184,7923,1304],{"class":229},[184,7925,7926],{"class":1795},"box",[184,7928,3449],{"class":229},[184,7930,7931,7935,7937,7940],{"class":186,"line":193},[184,7932,7934],{"class":7933},"s6YsC","    background-color",[184,7936,230],{"class":229},[184,7938,7939],{"class":1056}," $base_color",[184,7941,1321],{"class":229},[184,7943,7944,7947,7949,7952],{"class":186,"line":251},[184,7945,7946],{"class":7933},"    width",[184,7948,230],{"class":229},[184,7950,7951],{"class":267}," 20px",[184,7953,1321],{"class":229},[184,7955,7956,7959,7961,7963],{"class":186,"line":259},[184,7957,7958],{"class":7933},"    height",[184,7960,230],{"class":229},[184,7962,7951],{"class":267},[184,7964,1321],{"class":229},[184,7966,7967],{"class":186,"line":271},[184,7968,400],{"class":229},[165,7970,7973],{"className":7884,"code":7971,"filename":7972,"language":7887,"meta":173,"style":173},"@import '.\u002Fvariable.scss';\n@import '.\u002Fcomponent.scss';\n","style.scss",[138,7974,7975,7989],{"__ignoreMap":173},[184,7976,7977,7980,7982,7985,7987],{"class":186,"line":187},[184,7978,7979],{"class":2759},"@import",[184,7981,233],{"class":229},[184,7983,7984],{"class":236},".\u002Fvariable.scss",[184,7986,1260],{"class":229},[184,7988,1321],{"class":229},[184,7990,7991,7993,7995,7998,8000],{"class":186,"line":193},[184,7992,7979],{"class":2759},[184,7994,233],{"class":229},[184,7996,7997],{"class":236},".\u002Fcomponent.scss",[184,7999,1260],{"class":229},[184,8001,1321],{"class":229},[20,8003,8004,8006,8007,8009],{},[138,8005,6568],{},"もバンドルされた",[138,8008,7848],{},"を読み込むようにしましょう。",[165,8011,8014],{"className":977,"code":8012,"filename":8013,"language":980,"meta":173,"style":173},"\u003C!DOCTYPE html>\n\u003Chtml>\n    \u003Chead>\n        \u003Cmeta charset=\"utf-8\">\n        \u003Ctitle>webpackの練習\u003C\u002Ftitle>\n　　　　　\u003C!-- これ -->\n        \u003Clink rel=\"stylesheet\" href=\".\u002Fstyle.css\">\n    \u003C\u002Fhead>\n\n    \u003Cbody>\n        \u003Cmain>\n            \u003Cdiv id='app'>\n\n            \u003C\u002Fdiv>\n            \u003Cinput type=\"text\" value=\"\" id=\"inputs\">\n            \u003Cbutton id=\"submmit\" >追加する\u003C\u002Fbutton>\n            \u003Cdiv class='box'>\u003C\u002Fdiv>\n        \u003C\u002Fmain>\n    \u003C\u002Fbody>\n    \u003Cscript src='.\u002Fbundle.js'>\u003C\u002Fscript>\n\u003C\u002Fhtml>\n","dist\u002Findexx.html",[138,8015,8016,8026,8034,8042,8060,8076,8081,8110,8118,8122,8130,8138,8156,8160,8168,8202,8228,8251,8259,8267,8289],{"__ignoreMap":173},[184,8017,8018,8020,8022,8024],{"class":186,"line":187},[184,8019,987],{"class":229},[184,8021,990],{"class":225},[184,8023,994],{"class":993},[184,8025,997],{"class":229},[184,8027,8028,8030,8032],{"class":186,"line":193},[184,8029,1002],{"class":229},[184,8031,980],{"class":225},[184,8033,997],{"class":229},[184,8035,8036,8038,8040],{"class":186,"line":251},[184,8037,2973],{"class":229},[184,8039,1017],{"class":225},[184,8041,997],{"class":229},[184,8043,8044,8046,8048,8050,8052,8054,8056,8058],{"class":186,"line":259},[184,8045,2987],{"class":229},[184,8047,1027],{"class":225},[184,8049,1030],{"class":993},[184,8051,1033],{"class":229},[184,8053,1036],{"class":229},[184,8055,1039],{"class":236},[184,8057,1036],{"class":229},[184,8059,997],{"class":229},[184,8061,8062,8064,8066,8068,8070,8072,8074],{"class":186,"line":271},[184,8063,2987],{"class":229},[184,8065,1050],{"class":225},[184,8067,1053],{"class":229},[184,8069,6381],{"class":1056},[184,8071,1060],{"class":229},[184,8073,1050],{"class":225},[184,8075,997],{"class":229},[184,8077,8078],{"class":186,"line":279},[184,8079,8080],{"class":1069},"　　　　　\u003C!-- これ -->\n",[184,8082,8083,8085,8087,8089,8091,8093,8095,8097,8099,8101,8103,8106,8108],{"class":186,"line":4},[184,8084,2987],{"class":229},[184,8086,1077],{"class":225},[184,8088,1080],{"class":993},[184,8090,1033],{"class":229},[184,8092,1036],{"class":229},[184,8094,1087],{"class":236},[184,8096,1036],{"class":229},[184,8098,1092],{"class":993},[184,8100,1033],{"class":229},[184,8102,1036],{"class":229},[184,8104,8105],{"class":236},".\u002Fstyle.css",[184,8107,1036],{"class":229},[184,8109,997],{"class":229},[184,8111,8112,8114,8116],{"class":186,"line":305},[184,8113,3017],{"class":229},[184,8115,1017],{"class":225},[184,8117,997],{"class":229},[184,8119,8120],{"class":186,"line":313},[184,8121,367],{"emptyLinePlaceholder":366},[184,8123,8124,8126,8128],{"class":186,"line":321},[184,8125,2973],{"class":229},[184,8127,1123],{"class":225},[184,8129,997],{"class":229},[184,8131,8132,8134,8136],{"class":186,"line":329},[184,8133,2987],{"class":229},[184,8135,6414],{"class":225},[184,8137,997],{"class":229},[184,8139,8140,8142,8144,8146,8148,8150,8152,8154],{"class":186,"line":412},[184,8141,6421],{"class":229},[184,8143,13],{"class":225},[184,8145,1170],{"class":993},[184,8147,1033],{"class":229},[184,8149,1260],{"class":229},[184,8151,6432],{"class":236},[184,8153,1260],{"class":229},[184,8155,997],{"class":229},[184,8157,8158],{"class":186,"line":417},[184,8159,367],{"emptyLinePlaceholder":366},[184,8161,8162,8164,8166],{"class":186,"line":423},[184,8163,6445],{"class":229},[184,8165,13],{"class":225},[184,8167,997],{"class":229},[184,8169,8170,8172,8174,8176,8178,8180,8182,8184,8186,8188,8190,8192,8194,8196,8198,8200],{"class":186,"line":429},[184,8171,6421],{"class":229},[184,8173,6937],{"class":225},[184,8175,1222],{"class":993},[184,8177,1033],{"class":229},[184,8179,1036],{"class":229},[184,8181,170],{"class":236},[184,8183,1036],{"class":229},[184,8185,6950],{"class":993},[184,8187,1033],{"class":229},[184,8189,6955],{"class":229},[184,8191,1170],{"class":993},[184,8193,1033],{"class":229},[184,8195,1036],{"class":229},[184,8197,6964],{"class":236},[184,8199,1036],{"class":229},[184,8201,997],{"class":229},[184,8203,8204,8206,8208,8210,8212,8214,8216,8218,8220,8222,8224,8226],{"class":186,"line":435},[184,8205,6421],{"class":229},[184,8207,6975],{"class":225},[184,8209,1170],{"class":993},[184,8211,1033],{"class":229},[184,8213,1036],{"class":229},[184,8215,6984],{"class":236},[184,8217,1036],{"class":229},[184,8219,6989],{"class":229},[184,8221,6992],{"class":1056},[184,8223,1060],{"class":229},[184,8225,6975],{"class":225},[184,8227,997],{"class":229},[184,8229,8230,8232,8234,8237,8239,8241,8243,8245,8247,8249],{"class":186,"line":441},[184,8231,6421],{"class":229},[184,8233,13],{"class":225},[184,8235,8236],{"class":993}," class",[184,8238,1033],{"class":229},[184,8240,1260],{"class":229},[184,8242,7926],{"class":236},[184,8244,1260],{"class":229},[184,8246,1152],{"class":229},[184,8248,13],{"class":225},[184,8250,997],{"class":229},[184,8252,8253,8255,8257],{"class":186,"line":447},[184,8254,6454],{"class":229},[184,8256,6414],{"class":225},[184,8258,997],{"class":229},[184,8260,8261,8263,8265],{"class":186,"line":453},[184,8262,3017],{"class":229},[184,8264,1123],{"class":225},[184,8266,997],{"class":229},[184,8268,8269,8271,8273,8275,8277,8279,8281,8283,8285,8287],{"class":186,"line":459},[184,8270,2973],{"class":229},[184,8272,1137],{"class":225},[184,8274,1140],{"class":993},[184,8276,1033],{"class":229},[184,8278,1260],{"class":229},[184,8280,6481],{"class":236},[184,8282,1260],{"class":229},[184,8284,1152],{"class":229},[184,8286,1137],{"class":225},[184,8288,997],{"class":229},[184,8290,8291,8293,8295],{"class":186,"line":464},[184,8292,1060],{"class":229},[184,8294,980],{"class":225},[184,8296,997],{"class":229},[20,8298,8299,8300,8302],{},"そしてエントリーファイルであった",[138,8301,7549],{},"に以下のようにscssをインポートします。",[165,8304,8307],{"className":6503,"code":8305,"filename":8306,"language":6506,"meta":173,"style":173},"import $ from 'jquery';\nimport funcs from '.\u002Ffunctions';\nimport '~\u002Fsass\u002Fstyle.scss';\n\n$('#submmit').on('click',()=>{\n    return funcs.addNewText('#app','#inputs');\n})\n\n","src\u002Fjs\u002Fmain.js",[138,8308,8309,8325,8341,8354,8358,8390,8420],{"__ignoreMap":173},[184,8310,8311,8313,8315,8317,8319,8321,8323],{"class":186,"line":187},[184,8312,2819],{"class":2759},[184,8314,6515],{"class":1056},[184,8316,2825],{"class":2759},[184,8318,233],{"class":229},[184,8320,6310],{"class":236},[184,8322,1260],{"class":229},[184,8324,1321],{"class":229},[184,8326,8327,8329,8331,8333,8335,8337,8339],{"class":186,"line":193},[184,8328,2819],{"class":2759},[184,8330,7204],{"class":1056},[184,8332,2825],{"class":2759},[184,8334,233],{"class":229},[184,8336,7211],{"class":236},[184,8338,1260],{"class":229},[184,8340,1321],{"class":229},[184,8342,8343,8345,8347,8350,8352],{"class":186,"line":251},[184,8344,2819],{"class":2759},[184,8346,233],{"class":229},[184,8348,8349],{"class":236},"~\u002Fsass\u002Fstyle.scss",[184,8351,1260],{"class":229},[184,8353,1321],{"class":229},[184,8355,8356],{"class":186,"line":259},[184,8357,367],{"emptyLinePlaceholder":366},[184,8359,8360,8362,8364,8366,8368,8370,8372,8374,8376,8378,8380,8382,8384,8386,8388],{"class":186,"line":271},[184,8361,6534],{"class":1253},[184,8363,1257],{"class":1056},[184,8365,1260],{"class":229},[184,8367,7230],{"class":236},[184,8369,1260],{"class":229},[184,8371,1294],{"class":1056},[184,8373,1304],{"class":229},[184,8375,7239],{"class":1253},[184,8377,1257],{"class":1056},[184,8379,1260],{"class":229},[184,8381,7246],{"class":236},[184,8383,1260],{"class":229},[184,8385,7251],{"class":229},[184,8387,7254],{"class":993},[184,8389,3449],{"class":229},[184,8391,8392,8394,8396,8398,8400,8402,8404,8406,8408,8410,8412,8414,8416,8418],{"class":186,"line":279},[184,8393,7261],{"class":2759},[184,8395,7264],{"class":1056},[184,8397,1304],{"class":229},[184,8399,7269],{"class":1253},[184,8401,1257],{"class":225},[184,8403,1260],{"class":229},[184,8405,3482],{"class":236},[184,8407,1260],{"class":229},[184,8409,1267],{"class":229},[184,8411,1260],{"class":229},[184,8413,7284],{"class":236},[184,8415,1260],{"class":229},[184,8417,1294],{"class":225},[184,8419,1321],{"class":229},[184,8421,8422,8424],{"class":186,"line":4},[184,8423,3423],{"class":229},[184,8425,3426],{"class":1056},[20,8427,8428,8429,8431],{},"こうするとwebpackが",[138,8430,8349],{},"のインポートを捉え、適切にバンドルしてくれます。",[47,8433,8434],{"id":8434},"それぞれのモジュールの説明",[165,8436,8438],{"className":6503,"code":8437,"filename":3519,"language":6506,"meta":173,"style":173},"const MiniCssExtractPlugin = require('mini-css-extract-plugin');\n...\n...\nmodule: {\n    rules: [\n        {\n        test: \u002F\\.(sa|sc|c)ss$\u002F,\n        exclude: \u002Fnode_modules\u002F,\n        use: [\n            MiniCssExtractPlugin.loader,\n            {\n            loader: 'css-loader',\n            options: { url: false }\n            },\n            'sass-loader'\n        ]\n        }\n    ]\n},\n...\nplugins: [\n    new MiniCssExtractPlugin({\n    　　filename: 'style.css'\n    })\n]\n",[138,8439,8440,8462,8467,8471,8480,8489,8494,8527,8542,8551,8562,8567,8582,8599,8604,8613,8617,8621,8625,8629,8633,8642,8653,8666,8673],{"__ignoreMap":173},[184,8441,8442,8444,8446,8448,8450,8452,8454,8456,8458,8460],{"class":186,"line":187},[184,8443,2861],{"class":993},[184,8445,7506],{"class":1056},[184,8447,1033],{"class":229},[184,8449,3178],{"class":1253},[184,8451,1257],{"class":1056},[184,8453,1260],{"class":229},[184,8455,7470],{"class":236},[184,8457,1260],{"class":229},[184,8459,1294],{"class":1056},[184,8461,1321],{"class":229},[184,8463,8464],{"class":186,"line":193},[184,8465,8466],{"class":229},"...\n",[184,8468,8469],{"class":186,"line":251},[184,8470,8466],{"class":229},[184,8472,8473,8476,8478],{"class":186,"line":259},[184,8474,8475],{"class":1795},"module",[184,8477,230],{"class":229},[184,8479,1270],{"class":229},[184,8481,8482,8485,8487],{"class":186,"line":271},[184,8483,8484],{"class":1795},"    rules",[184,8486,230],{"class":229},[184,8488,2869],{"class":225},[184,8490,8491],{"class":186,"line":279},[184,8492,8493],{"class":229},"        {\n",[184,8495,8496,8499,8501,8503,8505,8507,8509,8511,8513,8515,8517,8519,8521,8523,8525],{"class":186,"line":4},[184,8497,8498],{"class":225},"        test",[184,8500,230],{"class":229},[184,8502,7614],{"class":229},[184,8504,7617],{"class":1056},[184,8506,1257],{"class":229},[184,8508,7622],{"class":236},[184,8510,7625],{"class":229},[184,8512,7628],{"class":236},[184,8514,7625],{"class":229},[184,8516,7633],{"class":236},[184,8518,1294],{"class":229},[184,8520,7638],{"class":236},[184,8522,6534],{"class":2759},[184,8524,7643],{"class":229},[184,8526,3457],{"class":229},[184,8528,8529,8532,8534,8536,8538,8540],{"class":186,"line":305},[184,8530,8531],{"class":225},"        exclude",[184,8533,230],{"class":229},[184,8535,7614],{"class":229},[184,8537,6564],{"class":236},[184,8539,7643],{"class":229},[184,8541,3457],{"class":229},[184,8543,8544,8547,8549],{"class":186,"line":313},[184,8545,8546],{"class":225},"        use",[184,8548,230],{"class":229},[184,8550,2869],{"class":225},[184,8552,8553,8556,8558,8560],{"class":186,"line":321},[184,8554,8555],{"class":1056},"            MiniCssExtractPlugin",[184,8557,1304],{"class":229},[184,8559,7679],{"class":1056},[184,8561,3457],{"class":229},[184,8563,8564],{"class":186,"line":329},[184,8565,8566],{"class":229},"            {\n",[184,8568,8569,8572,8574,8576,8578,8580],{"class":186,"line":412},[184,8570,8571],{"class":225},"            loader",[184,8573,230],{"class":229},[184,8575,233],{"class":229},[184,8577,7467],{"class":236},[184,8579,1260],{"class":229},[184,8581,3457],{"class":229},[184,8583,8584,8587,8589,8591,8593,8595,8597],{"class":186,"line":417},[184,8585,8586],{"class":225},"            options",[184,8588,230],{"class":229},[184,8590,7711],{"class":229},[184,8592,7714],{"class":225},[184,8594,230],{"class":229},[184,8596,7719],{"class":5557},[184,8598,7722],{"class":229},[184,8600,8601],{"class":186,"line":423},[184,8602,8603],{"class":229},"            },\n",[184,8605,8606,8609,8611],{"class":186,"line":429},[184,8607,8608],{"class":229},"            '",[184,8610,7461],{"class":236},[184,8612,240],{"class":229},[184,8614,8615],{"class":186,"line":435},[184,8616,7751],{"class":225},[184,8618,8619],{"class":186,"line":441},[184,8620,514],{"class":229},[184,8622,8623],{"class":186,"line":447},[184,8624,7861],{"class":225},[184,8626,8627],{"class":186,"line":453},[184,8628,2897],{"class":229},[184,8630,8631],{"class":186,"line":459},[184,8632,8466],{"class":229},[184,8634,8635,8638,8640],{"class":186,"line":464},[184,8636,8637],{"class":1795},"plugins",[184,8639,230],{"class":229},[184,8641,2869],{"class":1056},[184,8643,8644,8647,8649,8651],{"class":186,"line":470},[184,8645,8646],{"class":229},"    new",[184,8648,7832],{"class":1253},[184,8650,1257],{"class":1056},[184,8652,3449],{"class":229},[184,8654,8655,8658,8660,8662,8664],{"class":186,"line":476},[184,8656,8657],{"class":225},"    　　filename",[184,8659,230],{"class":229},[184,8661,233],{"class":229},[184,8663,7848],{"class":236},[184,8665,240],{"class":229},[184,8667,8668,8671],{"class":186,"line":481},[184,8669,8670],{"class":229},"    }",[184,8672,3426],{"class":1056},[184,8674,8675],{"class":186,"line":487},[184,8676,2927],{"class":1056},[20,8678,8679,8681,8682,8684,8685,8688],{},[138,8680,3519],{},"では特にこの箇所が重要になります。",[138,8683,8475],{},"の中身でsassや画像などのファイルを取り扱えるようになります。",[138,8686,8687],{},"rules","という箇所に取り扱うファイルの拡張子を正規表現で捉え、それに対するローダーの使用などを定義します。",[20,8690,8691,8694,8695,8697,8698,8701],{},[138,8692,8693],{},"MiniCssExtractPlugin","はcssをstyle.cssのような外部ファイルとして出力するために必要なプラグインです。",[138,8696,6300],{},"にて",[138,8699,8700],{},"import '~\u002Fsass\u002Fstyle.scss';","という記述があったと思います。このプラグインを使わない場合、webpackでのcssバンドルは外部ファイルでなく、HTMLに直接インライン記述をしようとします。（そうした方が処理と通信が早くなるらしい）",[20,8703,8704,8705,8707,8708,8710,8711,5154,8713,8715,8716,8718,8719,8721,8722,8725,8726,8728,8729,8732],{},"しかし外部ファイル",[138,8706,7848],{},"として今回は出力したいので、",[138,8709,8693],{},"を用いて",[138,8712,6300],{},[138,8714,8700],{},"のインポートを参考に、dist配下に",[138,8717,7848],{},"を出力します。ように外部ファイルとして出力します。",[138,8720,8693],{},"のローダーとプラグインに記述しておきます。プラグインの箇所では出力ファイル名を指定します。",[138,8723,8724],{},"filename: 'style.css'","としていますが、指定しない場合はエントリーファイルの",[138,8727,6300],{},"から",[138,8730,8731],{},"main.css","が出力されます。",[20,8734,8735],{},"以上でsassのバンドルができるようになりました。早速やってみます。",[165,8737,8740],{"className":8738,"code":8739,"language":170},[168],"npm run build\n",[138,8741,8739],{"__ignoreMap":173},[20,8743,8744],{},"するとdistにstyle.cssができました。中身をみてみるとsassで定義した依存性や変数が当てはまっています。",[27,8746,8747],{"id":8747},"基礎編終了",[20,8749,8750],{},"以上がwebpackを用いたjsファイルのバンドルとsassのコンパイルです。大切なのは5つの概念と必要なモジュールを読み込み、設定することです。次回は忌まわしきIEでjsが動くようにすること、画像の依存性解決、複数条件のバンドルを解説します。",[1530,8752,8753],{},"html pre.shiki code .sAklC, html code.shiki .sAklC{--shiki-default:#89DDFF}html pre.shiki code .s-wAU, html code.shiki .s-wAU{--shiki-default:#F07178}html pre.shiki code .sJ14y, html code.shiki .sJ14y{--shiki-default:#C792EA}html pre.shiki code .sfyAc, html code.shiki .sfyAc{--shiki-default:#C3E88D}html pre.shiki code .s0W1g, html code.shiki .s0W1g{--shiki-default:#BABED8}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s6cf3, html code.shiki .s6cf3{--shiki-default:#89DDFF;--shiki-default-font-style:italic}html pre.shiki code .sdLwU, html code.shiki .sdLwU{--shiki-default:#82AAFF}html pre.shiki code .sC9rS, html code.shiki .sC9rS{--shiki-default:#464B5D;--shiki-default-font-style:italic}html pre.shiki code .s7ZW3, html code.shiki .s7ZW3{--shiki-default:#BABED8;--shiki-default-font-style:italic}html pre.shiki code .sbqyR, html code.shiki .sbqyR{--shiki-default:#FF9CAC}html pre.shiki code .sx098, html code.shiki .sx098{--shiki-default:#F78C6C}html pre.shiki code .s5Dmg, html code.shiki .s5Dmg{--shiki-default:#FFCB6B}html pre.shiki code .s6YsC, html code.shiki .s6YsC{--shiki-default:#B2CCD6}",{"title":173,"searchDepth":251,"depth":251,"links":8755},[8756,8757,8758,8759,8762,8767],{"id":6221,"depth":193,"text":6221},{"id":6244,"depth":193,"text":6244},{"id":6275,"depth":193,"text":6276},{"id":6864,"depth":193,"text":6864,"children":8760},[8761],{"id":7338,"depth":251,"text":7338},{"id":7445,"depth":193,"text":7446,"children":8763},[8764,8765,8766],{"id":7452,"depth":251,"text":7453},{"id":7868,"depth":251,"text":7869},{"id":8434,"depth":251,"text":8434},{"id":8747,"depth":193,"text":8747},[1561],"2021-03-27","webpack基礎とSass・jsのバンドル",{},"\u002Fseries\u002Fwell-study-webpack-1",{"title":6171,"description":8770},"well-study-webpack","ちゃんと理解するWebpack5。","series\u002Fwell-study-webpack-1",[2752,6825],"_mix\u002Flogo-on-white-bg-768x299.png","tvz3F4ALarSfdIbpPp8wmTnJpLE5NRMu4vTA4PHN1Dc",{"id":8781,"title":8782,"body":8783,"category":10270,"createdAt":10271,"description":10272,"extension":1564,"index":187,"meta":10273,"navigation":366,"path":10274,"publish":366,"seo":10275,"series":10276,"seriesTitle":10272,"stem":10277,"tag":10278,"thumbnail":10280,"updatedAt":1575,"__hash__":10281},"series\u002Fseries\u002Fheadlesscms-strapi-1.md","headlessCMSのstrapi をnuxt.jsで静的書き出しを行う。その１：strapiとnuxtの連携",{"type":10,"value":8784,"toc":10242},[8785,8788,8791,8811,8814,8817,8828,8832,8838,8841,8847,8851,8854,8860,8863,8870,8881,8888,8891,8894,8900,8906,8909,8916,8919,8922,8925,8928,8931,8937,8947,8953,8956,8964,8967,8970,8973,8976,8980,8983,8989,8992,8998,9001,9004,9007,9010,9019,9022,9025,9028,9031,9034,9037,9040,9043,9049,9052,9055,9061,9064,9067,9070,9073,9090,9093,9096,9099,9102,9105,9108,9111,9114,9117,9120,9123,9126,9129,9133,9136,9139,9142,9146,9149,9152,9155,9158,9161,9172,9179,9183,9190,9193,9196,9200,9203,9503,9506,9635,9649,9652,9658,9663,9669,9683,9687,9690,10118,10121,10125,10128,10131,10139,10143,10146,10187,10194,10200,10207,10210,10213,10216,10219,10233,10236,10239],[20,8786,8787],{},"こんにちはjunです。新しいCMSを採用してみようという動きが社内であり、その過程で私がstrapiというheadlessCMSとnuxt.jsを用いて静的サイトを作成するとこまでやりました。",[20,8789,8790],{},"JAMstack構成でCMSを用いたwebサイトを作成できるということもあり、今までのCMSに変わっていく予感があったので調べてみました。シリーズ記事にして以下",[34,8792,8793,8796,8799,8802,8805,8808],{},[37,8794,8795],{},"headlessCMSとは何か、なぜ使うのか",[37,8797,8798],{},"strapiの使い方",[37,8800,8801],{},"strapiとnuxtの連携方法",[37,8803,8804],{},"strapiでブログライクなデータ構築",[37,8806,8807],{},"nuxtでの構築",[37,8809,8810],{},"静的書き出し",[20,8812,8813],{},"の６つを中心に説明していきます。また完成したフロントとバックのソースはgithubに上げる予定です。今回の記事では上３つを行います。",[20,8815,8816],{},"なお解説で登場するアプリケーションのバージョンは以下の通りです。",[34,8818,8819,8822,8825],{},[37,8820,8821],{},"node.js 13.12.0",[37,8823,8824],{},"strapi 3.2.5",[37,8826,8827],{},"nuxt 2.14.6",[27,8829,8831],{"id":8830},"headlesscmsって何","headlessCMSって何？",[20,8833,8834,8835],{},"headlessCMSのheadというのはビューのことを言います。ビューは今あなたが見ているこのサイトの見た目そのもの、HTMLのことです。そしてその ",[68,8836,8837],{},"ビューの構築がCMSから切り離されているのが、ビューの生成処理が存在しないのがheadlessCMSです。",[20,8839,8840],{},"wordpressなどの一般的なCMSはビューの生成をwordpressという１つのシステムで行っています。リクエストに応じて対応するデータを引っ張ってきて、HTMLを生成してレスポンスとして返します。",[20,8842,8843,8844],{},"しかしheadlessCMSのビューは ",[68,8845,8846],{},"独立したフロントエンド プロジェクトを立てて、そのプロジェクトからAPIを呼び出してデータを取得して、主にjsを用いてHTMLをレンダーします。",[47,8848,8850],{"id":8849},"どうしてヘッドをレスするの","どうしてヘッドをレスするの？",[20,8852,8853],{},"例えばwordpressの場合はHTML構造とデータの出力をwordpressというPHPシステム1つで行います。つまりフロントエンド の構築をPHPで行います。しかしその場合VueやReactを用いたフロントの構築は難しいですし、フロントはwordpressのシステムに従った構築を行う必要があります。最近のUIが優れたサイトや、生産的にフロントを作る場合は大変です。",[20,8855,8856,8857],{},"できるだけフロントはJSで構築したい！でもデータもバックから呼び出したい！そんな時にheadlessCMSを用います。 ",[68,8858,8859],{},"フロントがCMSから分離することでバックエンドの都合や制限を受けにくくなります。（いわゆる疎結合な状態）",[20,8861,8862],{},"例えばwordpressのテーマ構築にはindex.php、page.php、header\u002Ffooter.php、single.phpと言った決まりきったビューファイルがあります。フロント構築はこの制限されたファイルを上手く使用する必要があります。デザインの都合や仕様によっては開発が困難になったりします。",[20,8864,8865,8866,8869],{},"さらに ",[68,8867,8868],{},"Nuxt.jsやNext.jsを用いることで静的書き出しを行うことができます。"," 静的に書き出すことでCMSの課題であった不正ログインや攻撃のリスク、そしてメモリ消費による処理能力ダウンを防ぐことができます。（ただし構成と運用によります）",[34,8871,8872,8875,8878],{},[37,8873,8874],{},"jsフレームワークを用いてリッチで生産性高くフロントエンド を開発できる。",[37,8876,8877],{},"静的書き出しを行うことでセキュリティやパフォーマンスをあげることが可能",[37,8879,8880],{},"バックエンドへAPIを送ることでJSONでデータを受け取れる。JSONならば様々な媒体へデータを配信できるので、再利用性が高い（ブログの内容をスマホアプリに使用するなど）",[20,8882,8883,8884,8887],{},"上記の様な構成を ",[68,8885,8886],{},"JAMStack","、javaScript API Markup Stackといいます。",[27,8889,8890],{"id":8890},"strapiとは",[20,8892,8893],{},"headlessCMSはまだ登場してからまだ日が浅く、また種類も多いです。その中でも特に有名なのはContentful、strapiそして日本ではmicroCMSが人気です。",[20,8895,8896,8899],{},[68,8897,8898],{},"strapiはheadlessCMSの中でもオープンソースでありバックエンドを自由にカスタマイズ可能です。"," ContentfulとmicroCMSはそれらの会社がバックエンドをホスティングしており、指定のAPIキーとエンドポイントへAIPを送ります。その分お金はかかりますが、アーキテクチャによっては完全なサーバレス構成が可能になります。",[20,8901,8902,8903],{},"今回の記事では ",[68,8904,8905],{},"カスタマイズ性に優れたstrapiを用いて説明を行います。",[27,8907,8908],{"id":8908},"アプリの構成",[20,8910,8911,8912,8915],{},"今回の説明では以下の図の様に ",[68,8913,8914],{},"バックエンドにstrapi、フロントエンド にnuxt.jsを用いて構築した上で静的書き出しを行います。"," ローカルのプロジェクトでバックとフロントはポートを分けて実質別のサーバの様にします。そしてデータベースは用意が面倒だったのでMAMPのmysqlを使用しています。",[87,8917],{":src":8918,":width":6845,":center":1446},"'_mix\u002FheadlessFlow.png'",[20,8920,8921],{},"そしてお知らせの様な定型の項目に沿って入力するコンテンツだけでなく、今のCMSの様に自由に項目を入力できる様なページも作ろうと思います。",[27,8923,8924],{"id":8924},"strapiとnuxtのインストール",[47,8926,8927],{"id":8927},"バックエンドのstrapiをいれる",[20,8929,8930],{},"まずは適当にフロントとバックをいれるheadlessCMSディレクトリを作成",[165,8932,8935],{"className":8933,"code":8934,"language":170},[168],"~ % mkdir headless && cd headless\n",[138,8936,8934],{"__ignoreMap":173},[20,8938,8939,8940,8946],{},"(公式サイト)",[184,8941,8942],{},[201,8943,8944],{"href":8944,"rel":8945},"https:\u002F\u002Fstrapi.io\u002Fdocumentation\u002Fdeveloper-docs\u002Flatest\u002Fsetup-deployment-guides\u002Finstallation\u002Fcli.html#step-1-make-sure-requirements-are-met",[205],"にもある様にnpxを用いてstrapiをインストール。カスタムモードで行います。",[165,8948,8951],{"className":8949,"code":8950,"language":170},[168],"strapinpx create-strapi-app backend\n? Choose your installation type Custom (manual settings)\n? Choose your default database client mysql\n? Database name: strapi\n? Host: 127.0.0.1 (DBのホスト先）\n? Port: 8889 (DBのポート）\n? Username: (DBのユーザー）\n? Password: (DBのパスワード）\n? Enable SSL connection: No(開発環境だから）\n\nCreating a project with custom database options.\nCreating files.\nDependencies installed successfully.\n\nheadless % cd backend && npm run develop\n",[138,8952,8950],{"__ignoreMap":173},[47,8954,8955],{"id":8955},"strapiユーザーを作成する",[20,8957,1257,8958,8963],{},[201,8959,8962],{"href":8960,"rel":8961},"http:\u002F\u002Flocalhost:1337\u002Fadmin)%5Bhttp:\u002F\u002Flocalhost:1337\u002Fadmin",[205],"http:\u002F\u002Flocalhost:1337\u002Fadmin)[http:\u002F\u002Flocalhost:1337\u002Fadmin"," ]ビルドをすると1337のポートが開くので指示されたURLにアクセスします。すると以下の様なユーザー作成画面が表示されます。最初に作成されるユーザーはスーパーユーザーになります。",[87,8965],{":src":8966,":width":6845,":center":1446},"'_mix\u002Fstrapi_create_user-735x1024.png'",[20,8968,8969],{},"ここで任意の名前、アドレス、パスワードを設定します。「READY TO START」を押してログインします。",[87,8971],{":src":8972,":width":90},"'_mix\u002Fstapi_fitst.png'",[20,8974,8975],{},"最近になって日本語が当てられる様になったらしいですが、ほとんどが英語のままです。日本語表示はあまり期待しない方がいいです。とりあえずバックエンドはこれでインストール完了です。",[47,8977,8979],{"id":8978},"nuxtjs-のインストール","nuxt.js のインストール",[20,8981,8982],{},"ではフロントを構築するnuxt.jsを入れましょう。",[165,8984,8987],{"className":8985,"code":8986,"language":170},[168],"headless % npx create-nuxt-app frontend\n",[138,8988,8986],{"__ignoreMap":173},[20,8990,8991],{},"静的に書き出すのでUniversalモードでデプロイターゲットは Static を選びましょう。UIは作るのが面倒なのでbootstrap使います。あしからず。",[165,8993,8996],{"className":8994,"code":8995,"language":170},[168],"? Project name: frontend\n? Programming language: JavaScript\n? Package manager: Npm\n? UI framework: Bootstrap Vue\n? Testing framework: None\n? Rendering mode: Universal (SSR \u002F SSG)\n? Deployment target: Static (Static\u002FJAMStack hosting)\n? Development tools: (Press \u003Cspace> to select, \u003Ca> to toggle all, \u003Ci> to invert selection)\n? What is your GitHub username? jun\n? Version control system: Git\n\nSuccessfully created project frontend\n\nheadless % cd frontend && npm run dev\n",[138,8997,8995],{"__ignoreMap":173},[20,8999,9000],{},"npm run dev で開発サーバーを立ち上げます。localhost:3000 にアクセスするとお馴染みのnuxt.jsの画面がみれます。",[27,9002,9003],{"id":9003},"コンテンツタイプを作成する",[20,9005,9006],{},"フロントにデータを表示しようにも、バックにデータがなければ何も始まりません。まずはstrapiで表示するデータを作成します。そこでstrapiで「コンテンツタイプ」というものでコンテンツを定義してデータの型を作成します。",[20,9008,9009],{},"とりあえず以下の様なカラム を持つコンテンツ「お知らせ」を作ります。",[34,9011,9012,9014,9016],{},[37,9013,1700],{},[37,9015,1703],{},[37,9017,9018],{},"サムネイル",[20,9020,9021],{},"headlessCMSではまず最初に「どんなデータ構成を持つコンテンツを作るか？」ということを考えます。コンテンツを構成する要素を洗い出し、ユーザーが登録する項目を決定します。strapiでの操作はまず画面左の「Contents-Type Builder」をクリックします。画面が切り替わり、「コンテンツタイプ」と書かれているとこの「Create collection type」をクリックして追加します。",[87,9023],{":src":9024,":width":6073,":center":1446},"'_mix\u002Fsch-2020-11-03-0.00.31-300x197.png'",[20,9026,9027],{},"するとコンテンツタイプの名前をいれる様に言われますので、入力します。「お知らせ」なので「newsItem」としておきます。（なぜかタイトル名を「news」にすると400エラーになってしまいます。予約されている可能性あり。）",[87,9029],{":src":9030,":width":90},"'_mix\u002Fcreate_conten-768x283.png'",[20,9032,9033],{},"「続ける」をクリックすると次は項目とそのデータ型を登録します。",[87,9035],{":src":9036,":width":90},"'_mix\u002Fcreate_news_items-768x480.png'",[20,9038,9039],{},"タイトル→文字（text）、内容→リッチテキスト（Rich Text : Long Text)、サムネイル→画像（Media）とします。例えば「タイトル」は以下の通りです。",[87,9041],{":src":9042,":width":90},"'_mix\u002Fcreate_news_title-768x399.png'",[20,9044,9045,9048],{},[138,9046,9047],{},"Name","に入力された値はカラム名となるので日本語入力できません。引き続き入力する場合は「+Add another filed」で続け、項目の設定を終了する場合は「終了」を押します。ひとまず以下の様に設定します。",[87,9050],{":src":9051,":width":90},"'_mix\u002Fnews_ct-768x338.png'",[20,9053,9054],{},"そして「保存」を押すとこのコンテンツタイプが登録されます。ターミナルをみてみると",[165,9056,9059],{"className":9057,"code":9058,"language":170},[168],"[2020-11-02T16:43:29.201Z] info File created: \u002FUsers\u002Fjun\u002Fheadless\u002Fbackend\u002Fapi\u002Fnews-items\u002Fconfig\u002Froutes.json\n[2020-11-02T16:43:29.201Z] info File created: \u002FUsers\u002Fjun\u002Fheadless\u002Fbackend\u002Fapi\u002Fnews-items\u002Fcontrollers\u002Fnews-items.js\n[2020-11-02T16:43:29.201Z] info File created: \u002FUsers\u002Fjun\u002Fheadless\u002Fbackend\u002Fapi\u002Fnews-items\u002Fmodels\u002Fnews-items.js\n[2020-11-02T16:43:29.201Z] info File created: \u002FUsers\u002Fjun\u002Fheadless\u002Fbackend\u002Fapi\u002Fnews-items\u002Fmodels\u002Fnews-items.settings.json\n[2020-11-02T16:43:29.201Z] info File created: \u002FUsers\u002Fjun\u002Fheadless\u002Fbackend\u002Fapi\u002Fnews-items\u002Fservices\u002Fnews-items.js\n[2020-11-02T16:43:29.588Z] debug POST \u002Fcontent-type-builder\u002Fcontent-types (650 ms) 201\n[2020-11-02T16:43:29.614Z] info The server is restarting\n",[138,9060,9058],{"__ignoreMap":173},[20,9062,9063],{},"content-typeを作成するためのAPIが POSTされていることがわかります。実はstrapi内でもこの様にREST APIで呼び合ってコンテンツの編集を行っています。",[47,9065,9066],{"id":9066},"感覚的にはテーブルを作成",[20,9068,9069],{},"コンテンツタイプ作成の手順を一通りみてみるとDBでテーブルを作成したり、MVCフレームワークの「モデル」部分の実装と操作が似ています。というか同じです。コンテンツタイプでデータ項目と型を決定します。さらにstrapiではこのコンテンツタイプに応じたエンドポイントも作成してくれます。",[20,9071,9072],{},"headlessCMSはこの様に",[34,9074,9075,9078,9081,9084,9087],{},[37,9076,9077],{},"データテーブルとそのカラム・データ型の作成",[37,9079,9080],{},"エンドポイントの自動作成",[37,9082,9083],{},"エンドポイントの権限管理",[37,9085,9086],{},"自動マイグレーション",[37,9088,9089],{},"コンテンツタイプ（モデル）に応じたレコードの挿入、削除",[20,9091,9092],{},"これらを提供してくれます。",[47,9094,9095],{"id":9095},"お知らせを１件登録する",[20,9097,9098],{},"では早速レコードを１つ登録してみましょう。コレクションタイプ（画面左上）に「newsItem」が登録されているので、それをクリックします。すると以下の様な一覧画面が表示されます。",[87,9100],{":src":9101,":width":90},"'_mix\u002Fcreate_news_record-768x246.png'",[20,9103,9104],{},"レコードを登録するために右上の「newsItemを追加」をクリックします。すると先程コンテンツタイプ作成で定義した項目の入力欄が出現します。そこに値を入力します。",[87,9106],{":src":9107,":width":90},"'_mix\u002Fnews_input-768x263.png'",[20,9109,9110],{},"テキストエリア はプレーンテキスト、リッチテキストはマークダウンで入力します。画像を定義した箇所はファイルアップローダーが起動するので、ファイルをアップロードできますし、アップロードしたものを選択することもできます。",[20,9112,9113],{},"内容が入力し終わったら画面右上の「保存」をクリックします。そして保存終了後に隣の「Publish」をクリックしてこの登録したレコードが外部に公開できる様になります。",[20,9115,9116],{},"このPublishをクリックしないと、このデータをAPIで呼び出しても404が帰ってきますので注意。",[47,9118,9119],{"id":9119},"公開するためにもう一歩",[20,9121,9122],{},"現段階ではまだ先程登録したnewsItemは外部から呼び出せません。「設定」から「権限とロール」をクリックします。権限とロールでは公開するコンテンツタイプやPOST、 PUT、DELETEの権限設定を行えます。",[87,9124],{":src":9125,":width":90},"'_mix\u002Fpermission-768x223'",[20,9127,9128],{},"初期では「Authenticated（認証ユーザー）」「Public（匿名・全てのリクエスト）」のロールがあります。Publicをまずクリックすると、それぞれの権限設定が表示されます。",[87,9130],{":src":9131,":width":9132,":center":1446},"'_mix\u002Froles-768x634.png'","'600px'",[20,9134,9135],{},"登録したnewsItemの設定があります。ここで findとcount、findoneにチェックを入れます。そして「保存」を押します。 チェックした３つは読み取り専用です。ここでcreateやdeleteにチェックをいれると、APIを通じて誰もがデータを操作できてしまうので気をつけてください。",[20,9137,9138],{},"逆にstrapiで登録したユーザーが外部からデータを操作する場合は Authenticatedでチェックを入れます。私は面倒なのでAuthenticatedは Select all にして、Publicは読み取りのみにしています。",[20,9140,9141],{},"ちなみにロールは３つまで無料です。３ロール以上はなぜか課金が必要です。",[47,9143,9145],{"id":9144},"apiドキュメントプラグインをインストール","APIドキュメントプラグインをインストール",[20,9147,9148],{},"フロントの構築に移る前にAPIドキュメントプラグインを入れておきます。このプラグインは最初から入っておらず、「マーケットプレイス」から無料インストール可能です。このプラグインをいれるとコンテンツタイプに応じたAPIのエンドポイント一覧ドキュメントを自動で作成してくれます。",[87,9150],{":src":9151,":width":6073,":center":1446},"'_mix\u002Fdocument-300x206.png'",[20,9153,9154],{},"メニューにDocumentationが現れ、その画面から「Open the Documentation」でドキュメントが開きます。登録したnewsItemのAPIもあります。",[27,9156,9157],{"id":9157},"フロントから呼び出してみる",[20,9159,9160],{},"ドキュメントをみてみると",[34,9162,9163,9166,9169],{},[37,9164,9165],{},"\u002Fnews-items で一覧",[37,9167,9168],{},"\u002Fnews-items\u002Fcount で総数",[37,9170,9171],{},"\u002Fnews-items\u002F{id}　でidで紐づいたデータ",[20,9173,9174,9175,9178],{},"を取得することができます。idはstrapiのnewsItemの一覧画面で見れます。数字で表されます。さっき例で作ったのは最初なので ",[138,9176,9177],{},"\u002Fnews-items\u002F1"," で取得できます。",[47,9180,9182],{"id":9181},"talend-api-tester-でテスト","Talend API Tester でテスト",[20,9184,9185,9186,9189],{},"とりあえずAPIがきちんと呼び出せるか、内容が取れるかが確かめたい場合はTalend API Testerなどを使用してAPIをテストすることができます。以下の様に ",[138,9187,9188],{},"http:\u002F\u002Flocalhost:1337\u002Fnews-items\u002F1"," に対してAPIを投げてみると先程の登録した内容がレスポンスにJSONで戻ってきました。",[87,9191],{":src":9192,":width":90},"'_mix\u002Fapi_test-768x461.png'",[20,9194,9195],{},"Nuxtでの構築もこのJSONを元に作成します。",[47,9197,9199],{"id":9198},"strapi-nuxt-moduleをインストール","strapi nuxt moduleをインストール",[20,9201,9202],{},"nuxtでAPIベースの呼び出しを行う場合、呼び出し用のプラグインを自作することがあります。例えば以下の様な感じです。",[165,9204,9207],{"className":6503,"code":9205,"filename":9206,"language":6506,"meta":173,"style":173},"export default function(context,inject){\n    \u002F\u002F axios　インスタンス\n    const api = context.$axios.create({\n        timeout:5000,\n        headers:{\n            'Content-Type': 'application\u002Fjson',\n            'X-Requested-With': 'XMLHttpRequest',\n        }\n    })\n\n    \u002F\u002F APIの呼び出し先を設定\n    api.setBaseURL(context.env.apiBaseURL);\n\n \n    api.onRequest(config=>{\n        \u002F\u002F リクエスト時の処理\n    })\n\n\n    api.onResponseError(err=>{\n        switch(err.response.status){\n            \u002F\u002F ステータスごとに異なる処理\n        }\n    })\n\n    \u002F\u002F こうすると $API で上記の設定をしたaxiosインスタンスを呼び出せる。つまり通信処理を共通化できる。\n    inject('API',api);\n}\n","plugins\u002Faxios.js",[138,9208,9209,9229,9234,9261,9273,9281,9301,9319,9323,9329,9333,9338,9366,9370,9374,9392,9397,9403,9407,9411,9429,9452,9457,9461,9467,9471,9476,9499],{"__ignoreMap":173},[184,9210,9211,9213,9215,9217,9219,9222,9224,9227],{"class":186,"line":187},[184,9212,2760],{"class":2759},[184,9214,2763],{"class":2759},[184,9216,1280],{"class":993},[184,9218,1257],{"class":229},[184,9220,9221],{"class":1285},"context",[184,9223,1267],{"class":229},[184,9225,9226],{"class":1285},"inject",[184,9228,7064],{"class":229},[184,9230,9231],{"class":186,"line":193},[184,9232,9233],{"class":1069},"    \u002F\u002F axios　インスタンス\n",[184,9235,9236,9239,9242,9244,9247,9249,9252,9254,9257,9259],{"class":186,"line":251},[184,9237,9238],{"class":993},"    const",[184,9240,9241],{"class":1056}," api",[184,9243,6002],{"class":229},[184,9245,9246],{"class":1056}," context",[184,9248,1304],{"class":229},[184,9250,9251],{"class":1056},"$axios",[184,9253,1304],{"class":229},[184,9255,9256],{"class":1253},"create",[184,9258,1257],{"class":225},[184,9260,3449],{"class":229},[184,9262,9263,9266,9268,9271],{"class":186,"line":259},[184,9264,9265],{"class":225},"        timeout",[184,9267,230],{"class":229},[184,9269,9270],{"class":267},"5000",[184,9272,3457],{"class":229},[184,9274,9275,9278],{"class":186,"line":271},[184,9276,9277],{"class":225},"        headers",[184,9279,9280],{"class":229},":{\n",[184,9282,9283,9285,9288,9290,9292,9294,9297,9299],{"class":186,"line":279},[184,9284,8608],{"class":229},[184,9286,9287],{"class":225},"Content-Type",[184,9289,1260],{"class":229},[184,9291,230],{"class":229},[184,9293,233],{"class":229},[184,9295,9296],{"class":236},"application\u002Fjson",[184,9298,1260],{"class":229},[184,9300,3457],{"class":229},[184,9302,9303,9305,9307,9309,9311,9313,9315,9317],{"class":186,"line":4},[184,9304,8608],{"class":229},[184,9306,3219],{"class":225},[184,9308,1260],{"class":229},[184,9310,230],{"class":229},[184,9312,233],{"class":229},[184,9314,3231],{"class":236},[184,9316,1260],{"class":229},[184,9318,3457],{"class":229},[184,9320,9321],{"class":186,"line":305},[184,9322,514],{"class":229},[184,9324,9325,9327],{"class":186,"line":313},[184,9326,8670],{"class":229},[184,9328,3426],{"class":225},[184,9330,9331],{"class":186,"line":321},[184,9332,367],{"emptyLinePlaceholder":366},[184,9334,9335],{"class":186,"line":329},[184,9336,9337],{"class":1069},"    \u002F\u002F APIの呼び出し先を設定\n",[184,9339,9340,9343,9345,9348,9350,9352,9354,9357,9359,9362,9364],{"class":186,"line":412},[184,9341,9342],{"class":1056},"    api",[184,9344,1304],{"class":229},[184,9346,9347],{"class":1253},"setBaseURL",[184,9349,1257],{"class":225},[184,9351,9221],{"class":1056},[184,9353,1304],{"class":229},[184,9355,9356],{"class":1056},"env",[184,9358,1304],{"class":229},[184,9360,9361],{"class":1056},"apiBaseURL",[184,9363,1294],{"class":225},[184,9365,1321],{"class":229},[184,9367,9368],{"class":186,"line":417},[184,9369,367],{"emptyLinePlaceholder":366},[184,9371,9372],{"class":186,"line":423},[184,9373,2048],{"class":225},[184,9375,9376,9378,9380,9383,9385,9388,9390],{"class":186,"line":429},[184,9377,9342],{"class":1056},[184,9379,1304],{"class":229},[184,9381,9382],{"class":1253},"onRequest",[184,9384,1257],{"class":225},[184,9386,9387],{"class":1285},"config",[184,9389,7254],{"class":993},[184,9391,3449],{"class":229},[184,9393,9394],{"class":186,"line":435},[184,9395,9396],{"class":1069},"        \u002F\u002F リクエスト時の処理\n",[184,9398,9399,9401],{"class":186,"line":441},[184,9400,8670],{"class":229},[184,9402,3426],{"class":225},[184,9404,9405],{"class":186,"line":447},[184,9406,367],{"emptyLinePlaceholder":366},[184,9408,9409],{"class":186,"line":453},[184,9410,367],{"emptyLinePlaceholder":366},[184,9412,9413,9415,9417,9420,9422,9425,9427],{"class":186,"line":459},[184,9414,9342],{"class":1056},[184,9416,1304],{"class":229},[184,9418,9419],{"class":1253},"onResponseError",[184,9421,1257],{"class":225},[184,9423,9424],{"class":1285},"err",[184,9426,7254],{"class":993},[184,9428,3449],{"class":229},[184,9430,9431,9434,9436,9438,9440,9443,9445,9448,9450],{"class":186,"line":464},[184,9432,9433],{"class":2759},"        switch",[184,9435,1257],{"class":225},[184,9437,9424],{"class":1056},[184,9439,1304],{"class":229},[184,9441,9442],{"class":1056},"response",[184,9444,1304],{"class":229},[184,9446,9447],{"class":1056},"status",[184,9449,1294],{"class":225},[184,9451,3449],{"class":229},[184,9453,9454],{"class":186,"line":470},[184,9455,9456],{"class":1069},"            \u002F\u002F ステータスごとに異なる処理\n",[184,9458,9459],{"class":186,"line":476},[184,9460,514],{"class":229},[184,9462,9463,9465],{"class":186,"line":481},[184,9464,8670],{"class":229},[184,9466,3426],{"class":225},[184,9468,9469],{"class":186,"line":487},[184,9470,367],{"emptyLinePlaceholder":366},[184,9472,9473],{"class":186,"line":493},[184,9474,9475],{"class":1069},"    \u002F\u002F こうすると $API で上記の設定をしたaxiosインスタンスを呼び出せる。つまり通信処理を共通化できる。\n",[184,9477,9478,9481,9483,9485,9488,9490,9492,9495,9497],{"class":186,"line":499},[184,9479,9480],{"class":1253},"    inject",[184,9482,1257],{"class":225},[184,9484,1260],{"class":229},[184,9486,9487],{"class":236},"API",[184,9489,1260],{"class":229},[184,9491,1267],{"class":229},[184,9493,9494],{"class":1056},"api",[184,9496,1294],{"class":225},[184,9498,1321],{"class":229},[184,9500,9501],{"class":186,"line":505},[184,9502,400],{"class":229},[20,9504,9505],{},"こうすることでAPIの呼び出し処理を共通化できます。しかしstrapiには上記の様な strapi nuxt module というものがあります。そのモジュールを用いると以下の様に呼び出しが可能です。",[165,9507,9509],{"className":6503,"code":9508,"language":6506,"meta":173,"style":173},"async asyncData(){\n    await this.$strapi.findOne('newsItem',1)\n        .then(async res=>{\n            \u002F\u002F成功時の処理 res にstrapi からのデータが入っている\n        })\n        .catch(err=>{\n　　　　　　　\u002F\u002Fエラー時の処理\n            console.log(error);\n        })\n}\n",[138,9510,9511,9523,9555,9575,9580,9587,9602,9607,9625,9631],{"__ignoreMap":173},[184,9512,9513,9516,9519,9521],{"class":186,"line":187},[184,9514,9515],{"class":1056},"async ",[184,9517,9518],{"class":1253},"asyncData",[184,9520,3696],{"class":1056},[184,9522,3449],{"class":229},[184,9524,9525,9528,9531,9534,9536,9539,9541,9543,9546,9548,9550,9553],{"class":186,"line":193},[184,9526,9527],{"class":2759},"    await",[184,9529,9530],{"class":229}," this.",[184,9532,9533],{"class":1056},"$strapi",[184,9535,1304],{"class":229},[184,9537,9538],{"class":1253},"findOne",[184,9540,1257],{"class":225},[184,9542,1260],{"class":229},[184,9544,9545],{"class":236},"newsItem",[184,9547,1260],{"class":229},[184,9549,1267],{"class":229},[184,9551,9552],{"class":267},"1",[184,9554,3426],{"class":225},[184,9556,9557,9560,9563,9565,9568,9571,9573],{"class":186,"line":251},[184,9558,9559],{"class":229},"        .",[184,9561,9562],{"class":1253},"then",[184,9564,1257],{"class":225},[184,9566,9567],{"class":993},"async",[184,9569,9570],{"class":1285}," res",[184,9572,7254],{"class":993},[184,9574,3449],{"class":229},[184,9576,9577],{"class":186,"line":259},[184,9578,9579],{"class":1069},"            \u002F\u002F成功時の処理 res にstrapi からのデータが入っている\n",[184,9581,9582,9585],{"class":186,"line":271},[184,9583,9584],{"class":229},"        }",[184,9586,3426],{"class":225},[184,9588,9589,9591,9594,9596,9598,9600],{"class":186,"line":279},[184,9590,9559],{"class":229},[184,9592,9593],{"class":1253},"catch",[184,9595,1257],{"class":225},[184,9597,9424],{"class":1285},[184,9599,7254],{"class":993},[184,9601,3449],{"class":229},[184,9603,9604],{"class":186,"line":4},[184,9605,9606],{"class":1069},"　　　　　　　\u002F\u002Fエラー時の処理\n",[184,9608,9609,9612,9614,9616,9618,9621,9623],{"class":186,"line":305},[184,9610,9611],{"class":1056},"            console",[184,9613,1304],{"class":229},[184,9615,1307],{"class":1253},[184,9617,1257],{"class":225},[184,9619,9620],{"class":1056},"error",[184,9622,1294],{"class":225},[184,9624,1321],{"class":229},[184,9626,9627,9629],{"class":186,"line":313},[184,9628,9584],{"class":229},[184,9630,3426],{"class":225},[184,9632,9633],{"class":186,"line":321},[184,9634,400],{"class":229},[20,9636,9637,9639,9640,9643,9644],{},[138,9638,9533],{}," というコンテキストが自動で追加され ",[138,9641,9642],{},"findOne('contens-type-name',id)","で呼び出すことができます。loginや認証ユーザーのAPIもあるのでnuxt strapiモジュールを入れておくと構築がしやすくなります。",[201,9645,9648],{"href":9646,"rel":9647},"https:\u002F\u002Fstrapi.nuxtjs.org\u002F",[205],"公式サイト",[20,9650,9651],{},"npm でインストールしておきます。",[165,9653,9656],{"className":9654,"code":9655,"language":170},[168],"npm install @nuxtjs\u002Fstrapi\n",[138,9657,9655],{"__ignoreMap":173},[20,9659,9660,9662],{},[138,9661,5709],{},"で以下の様に追記します。",[165,9664,9667],{"className":9665,"code":9666,"language":170},[168],"modules: [\n  '@nuxtjs\u002Fstrapi',\n],\nstrapi: {\n  url:'http:\u002F\u002Flocalhost:1337'\n},\n",[138,9668,9666],{"__ignoreMap":173},[20,9670,9671,9672,9674,9675,9678,9679,9682],{},"こうするとモジュールが有効になり、",[138,9673,9533],{},"のコンテキストが使用可能になります。",[138,9676,9677],{},"strapi:{}","でオプションが指定できます。strapiがホストされているurlを書いておきます。開発版なので",[138,9680,9681],{},"http:\u002F\u002Flocalhost:1337","にしていますが実際の運用ではenvファイルに書いておくなりしておきましょう。",[47,9684,9686],{"id":9685},"とりあえず-初期画面に表示してみる","とりあえず 初期画面に表示してみる",[20,9688,9689],{},"nuxt.jsをインストールした時に既に存在している pages\u002Findex.vue に登録したニュースをboostrap のcardを用いて表示させてみましょう。以下の様に記述",[165,9691,9694],{"className":2955,"code":9692,"filename":9693,"language":2701,"meta":173,"style":173},"\u003Ctemplate>\n  \u003Cdiv class=\"container\">\n    \u003Cdiv>\n      \u003Cb-card\n        :title=\"title\"\n        :img-src=\"img\"\n        img-top\n        tag=\"article\"\n        style=\"max-width: 20rem;\"\n        class=\"my-5\"\n      >\n        \u003Cb-card-text>\n          {{content}}\n        \u003C\u002Fb-card-text>\n      \u003C\u002Fb-card>\n    \u003C\u002Fdiv>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n\u003Cscript>\nexport default {\n  data(){\n    return{\n\n    }\n  },\n  async asyncData(context){\n    return await context.$strapi.findOne('news-items',1)\n      .then(res=>{\n        return {\n          title:res.title,\n          img:context.env.envSet.IMG_BASE_URL+res.thumbnail.url,\n          content:res.conctent\n        }\n      })\n      .catch(err=>{\n        console.error(err)\n      })\n  }\n}\n\u003C\u002Fscript>\n","pages\u002Findex.vue",[138,9695,9696,9704,9723,9731,9739,9752,9766,9771,9785,9799,9813,9818,9827,9832,9840,9850,9858,9867,9875,9879,9887,9895,9903,9909,9913,9917,9922,9935,9966,9982,9988,10003,10042,10056,10060,10066,10080,10095,10101,10106,10110],{"__ignoreMap":173},[184,9697,9698,9700,9702],{"class":186,"line":187},[184,9699,1002],{"class":229},[184,9701,2966],{"class":225},[184,9703,997],{"class":229},[184,9705,9706,9708,9710,9712,9714,9716,9719,9721],{"class":186,"line":193},[184,9707,1024],{"class":229},[184,9709,13],{"class":225},[184,9711,8236],{"class":993},[184,9713,1033],{"class":229},[184,9715,1036],{"class":229},[184,9717,9718],{"class":236},"container",[184,9720,1036],{"class":229},[184,9722,997],{"class":229},[184,9724,9725,9727,9729],{"class":186,"line":251},[184,9726,2973],{"class":229},[184,9728,13],{"class":225},[184,9730,997],{"class":229},[184,9732,9733,9736],{"class":186,"line":259},[184,9734,9735],{"class":229},"      \u003C",[184,9737,9738],{"class":225},"b-card\n",[184,9740,9741,9744,9746,9748,9750],{"class":186,"line":271},[184,9742,9743],{"class":993},"        :title",[184,9745,1033],{"class":229},[184,9747,1036],{"class":229},[184,9749,1050],{"class":236},[184,9751,291],{"class":229},[184,9753,9754,9757,9759,9761,9764],{"class":186,"line":279},[184,9755,9756],{"class":993},"        :img-src",[184,9758,1033],{"class":229},[184,9760,1036],{"class":229},[184,9762,9763],{"class":236},"img",[184,9765,291],{"class":229},[184,9767,9768],{"class":186,"line":4},[184,9769,9770],{"class":993},"        img-top\n",[184,9772,9773,9776,9778,9780,9783],{"class":186,"line":305},[184,9774,9775],{"class":993},"        tag",[184,9777,1033],{"class":229},[184,9779,1036],{"class":229},[184,9781,9782],{"class":236},"article",[184,9784,291],{"class":229},[184,9786,9787,9790,9792,9794,9797],{"class":186,"line":313},[184,9788,9789],{"class":993},"        style",[184,9791,1033],{"class":229},[184,9793,1036],{"class":229},[184,9795,9796],{"class":236},"max-width: 20rem;",[184,9798,291],{"class":229},[184,9800,9801,9804,9806,9808,9811],{"class":186,"line":321},[184,9802,9803],{"class":993},"        class",[184,9805,1033],{"class":229},[184,9807,1036],{"class":229},[184,9809,9810],{"class":236},"my-5",[184,9812,291],{"class":229},[184,9814,9815],{"class":186,"line":329},[184,9816,9817],{"class":229},"      >\n",[184,9819,9820,9822,9825],{"class":186,"line":412},[184,9821,2987],{"class":229},[184,9823,9824],{"class":225},"b-card-text",[184,9826,997],{"class":229},[184,9828,9829],{"class":186,"line":417},[184,9830,9831],{"class":1056},"          {{content}}\n",[184,9833,9834,9836,9838],{"class":186,"line":423},[184,9835,6454],{"class":229},[184,9837,9824],{"class":225},[184,9839,997],{"class":229},[184,9841,9842,9845,9848],{"class":186,"line":429},[184,9843,9844],{"class":229},"      \u003C\u002F",[184,9846,9847],{"class":225},"b-card",[184,9849,997],{"class":229},[184,9851,9852,9854,9856],{"class":186,"line":435},[184,9853,3017],{"class":229},[184,9855,13],{"class":225},[184,9857,997],{"class":229},[184,9859,9860,9863,9865],{"class":186,"line":441},[184,9861,9862],{"class":229},"  \u003C\u002F",[184,9864,13],{"class":225},[184,9866,997],{"class":229},[184,9868,9869,9871,9873],{"class":186,"line":447},[184,9870,1060],{"class":229},[184,9872,2966],{"class":225},[184,9874,997],{"class":229},[184,9876,9877],{"class":186,"line":453},[184,9878,367],{"emptyLinePlaceholder":366},[184,9880,9881,9883,9885],{"class":186,"line":459},[184,9882,1002],{"class":229},[184,9884,1137],{"class":225},[184,9886,997],{"class":229},[184,9888,9889,9891,9893],{"class":186,"line":464},[184,9890,2760],{"class":2759},[184,9892,2763],{"class":2759},[184,9894,1270],{"class":229},[184,9896,9897,9900],{"class":186,"line":470},[184,9898,9899],{"class":225},"  data",[184,9901,9902],{"class":229},"(){\n",[184,9904,9905,9907],{"class":186,"line":476},[184,9906,7261],{"class":2759},[184,9908,3449],{"class":229},[184,9910,9911],{"class":186,"line":481},[184,9912,367],{"emptyLinePlaceholder":366},[184,9914,9915],{"class":186,"line":487},[184,9916,520],{"class":229},[184,9918,9919],{"class":186,"line":493},[184,9920,9921],{"class":229},"  },\n",[184,9923,9924,9927,9929,9931,9933],{"class":186,"line":499},[184,9925,9926],{"class":993},"  async",[184,9928,5967],{"class":225},[184,9930,1257],{"class":229},[184,9932,9221],{"class":1285},[184,9934,7064],{"class":229},[184,9936,9937,9939,9941,9943,9945,9947,9949,9951,9953,9955,9958,9960,9962,9964],{"class":186,"line":505},[184,9938,7261],{"class":2759},[184,9940,6005],{"class":2759},[184,9942,9246],{"class":1056},[184,9944,1304],{"class":229},[184,9946,9533],{"class":1056},[184,9948,1304],{"class":229},[184,9950,9538],{"class":1253},[184,9952,1257],{"class":225},[184,9954,1260],{"class":229},[184,9956,9957],{"class":236},"news-items",[184,9959,1260],{"class":229},[184,9961,1267],{"class":229},[184,9963,9552],{"class":267},[184,9965,3426],{"class":225},[184,9967,9968,9971,9973,9975,9978,9980],{"class":186,"line":511},[184,9969,9970],{"class":229},"      .",[184,9972,9562],{"class":1253},[184,9974,1257],{"class":225},[184,9976,9977],{"class":1285},"res",[184,9979,7254],{"class":993},[184,9981,3449],{"class":229},[184,9983,9984,9986],{"class":186,"line":517},[184,9985,6036],{"class":2759},[184,9987,1270],{"class":229},[184,9989,9990,9993,9995,9997,9999,10001],{"class":186,"line":523},[184,9991,9992],{"class":225},"          title",[184,9994,230],{"class":229},[184,9996,9977],{"class":1056},[184,9998,1304],{"class":229},[184,10000,1050],{"class":1056},[184,10002,3457],{"class":229},[184,10004,10005,10008,10010,10012,10014,10016,10018,10021,10023,10026,10028,10030,10032,10035,10037,10040],{"class":186,"line":528},[184,10006,10007],{"class":225},"          img",[184,10009,230],{"class":229},[184,10011,9221],{"class":1056},[184,10013,1304],{"class":229},[184,10015,9356],{"class":1056},[184,10017,1304],{"class":229},[184,10019,10020],{"class":1056},"envSet",[184,10022,1304],{"class":229},[184,10024,10025],{"class":1056},"IMG_BASE_URL",[184,10027,7120],{"class":229},[184,10029,9977],{"class":1056},[184,10031,1304],{"class":229},[184,10033,10034],{"class":1056},"thumbnail",[184,10036,1304],{"class":229},[184,10038,10039],{"class":1056},"url",[184,10041,3457],{"class":229},[184,10043,10044,10047,10049,10051,10053],{"class":186,"line":533},[184,10045,10046],{"class":225},"          content",[184,10048,230],{"class":229},[184,10050,9977],{"class":1056},[184,10052,1304],{"class":229},[184,10054,10055],{"class":1056},"conctent\n",[184,10057,10058],{"class":186,"line":538},[184,10059,514],{"class":229},[184,10061,10062,10064],{"class":186,"line":543},[184,10063,1330],{"class":229},[184,10065,3426],{"class":225},[184,10067,10068,10070,10072,10074,10076,10078],{"class":186,"line":549},[184,10069,9970],{"class":229},[184,10071,9593],{"class":1253},[184,10073,1257],{"class":225},[184,10075,9424],{"class":1285},[184,10077,7254],{"class":993},[184,10079,3449],{"class":229},[184,10081,10082,10085,10087,10089,10091,10093],{"class":186,"line":555},[184,10083,10084],{"class":1056},"        console",[184,10086,1304],{"class":229},[184,10088,9620],{"class":1253},[184,10090,1257],{"class":225},[184,10092,9424],{"class":1056},[184,10094,3426],{"class":225},[184,10096,10097,10099],{"class":186,"line":561},[184,10098,1330],{"class":229},[184,10100,3426],{"class":225},[184,10102,10103],{"class":186,"line":566},[184,10104,10105],{"class":229},"  }\n",[184,10107,10108],{"class":186,"line":572},[184,10109,400],{"class":229},[184,10111,10112,10114,10116],{"class":186,"line":578},[184,10113,1060],{"class":229},[184,10115,1137],{"class":225},[184,10117,997],{"class":229},[20,10119,10120],{},"いらないものはとりあえず省きましました。コードの解説をします。",[342,10122,10124],{"id":10123},"asyncdataでapiを呼ぶ","asyncDataでAPIを呼ぶ",[20,10126,10127],{},"asyncDataというのはssr・ssgモードのpages配下で利用できます。これはcreatedよりも前、コンポーネントインスタンスが完成する際に呼び出せます。このasycData内の処理が終わってからコンポーネントが作成されます。",[20,10129,10130],{},"静的出力・ssrの場合はasycDataに処理を書くことでサーバーサイドでAPIの呼び出し、そしてコンポーネントのdataへのマージを行ってくれます。createdでやると、静的出力した際になんと静的htmlからAPIを呼び出してしまいます。",[20,10132,10133,10134],{},"詳しくは",[201,10135,10138],{"href":10136,"rel":10137},"https:\u002F\u002Fnuxtjs.org\u002Fdocs\u002F2.x\u002Ffeatures\u002Fdata-fetching#async-data",[205],"こちら",[342,10140,10142],{"id":10141},"画像のurlはちょっと注意","画像のURLはちょっと注意",[20,10144,10145],{},"APIで呼び出した際に、サムネイルのURLは以下の様になっていました。",[165,10147,10149],{"className":6739,"code":10148,"language":6741,"meta":173,"style":173},"thumbnail:{\n...\nurl\": \"\u002Fuploads\u002Fnuxt_strapi_8a86d72c14.png\",\n...\n}\n",[138,10150,10151,10158,10162,10179,10183],{"__ignoreMap":173},[184,10152,10153,10156],{"class":186,"line":187},[184,10154,10155],{"class":1056},"thumbnail:",[184,10157,3449],{"class":229},[184,10159,10160],{"class":186,"line":193},[184,10161,8466],{"class":1056},[184,10163,10164,10166,10168,10170,10172,10175,10177],{"class":186,"line":251},[184,10165,10039],{"class":1056},[184,10167,1036],{"class":229},[184,10169,6755],{"class":993},[184,10171,1036],{"class":229},[184,10173,10174],{"class":1056},"\u002Fuploads\u002Fnuxt_strapi_8a86d72c14.png",[184,10176,1036],{"class":229},[184,10178,3457],{"class":993},[184,10180,10181],{"class":186,"line":259},[184,10182,8466],{"class":993},[184,10184,10185],{"class":186,"line":271},[184,10186,400],{"class":993},[20,10188,10189,10190,10193],{},"ローカルの環境では画像にアクセスできません。（実物が",[138,10191,10192],{},"http:\u002F\u002Flocalhost:1337\u002F","配下にいるため）そのためenvを用いて",[165,10195,10198],{"className":10196,"code":10197,"language":170},[168],"img:context.env.envSet.IMG_BASE_URL+res.thumbnail.url,\n",[138,10199,10197],{"__ignoreMap":173},[20,10201,10202,10203,10206],{},"としてURLの前に",[138,10204,10205],{},"env.envSet.IMG_BASE_URL = http:\u002F\u002Flocalhost:1337","が出力される様にしています。そして表示されたページが以下の感じです。",[87,10208],{":src":10209,":width":6073,":center":1446},"'_mix\u002Ffinal-768x414.png'",[20,10211,10212],{},"きちんとデータ・画像がとってこれていますし、bootstrapにはめ込まれていい感じです。",[342,10214,10215],{"id":10215},"データ取得できない場合",[20,10217,10218],{},"データが取得できない場合以下のことを確認",[34,10220,10221,10224,10227,10230],{},[37,10222,10223],{},"レコードは「Publish」になっているか",[37,10225,10226],{},"権限とロールでPublicの読み取り権限にチェックを入れているか",[37,10228,10229],{},"エンドポイントを間違っていないか",[37,10231,10232],{},"strapiのサーバを落としていないか",[27,10234,10235],{"id":3865},"次回は…",[20,10237,10238],{},"今回の記事ではstrapiとNuxtのインストール・連携まで行いました。次回の記事からはブログ構成を構築するためのstrapiで「ページ」データの構築、そしてnuxtでの出力方法を書いていきます。",[1530,10240,10241],{},"html pre.shiki code .s6cf3, html code.shiki .s6cf3{--shiki-default:#89DDFF;--shiki-default-font-style:italic}html pre.shiki code .sJ14y, html code.shiki .sJ14y{--shiki-default:#C792EA}html pre.shiki code .sAklC, html code.shiki .sAklC{--shiki-default:#89DDFF}html pre.shiki code .s7ZW3, html code.shiki .s7ZW3{--shiki-default:#BABED8;--shiki-default-font-style:italic}html pre.shiki code .sC9rS, html code.shiki .sC9rS{--shiki-default:#464B5D;--shiki-default-font-style:italic}html pre.shiki code .s0W1g, html code.shiki .s0W1g{--shiki-default:#BABED8}html pre.shiki code .sdLwU, html code.shiki .sdLwU{--shiki-default:#82AAFF}html pre.shiki code .s-wAU, html code.shiki .s-wAU{--shiki-default:#F07178}html pre.shiki code .sx098, html code.shiki .sx098{--shiki-default:#F78C6C}html pre.shiki code .sfyAc, html code.shiki .sfyAc{--shiki-default:#C3E88D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":173,"searchDepth":251,"depth":251,"links":10243},[10244,10247,10248,10249,10254,10260,10269],{"id":8830,"depth":193,"text":8831,"children":10245},[10246],{"id":8849,"depth":251,"text":8850},{"id":8890,"depth":193,"text":8890},{"id":8908,"depth":193,"text":8908},{"id":8924,"depth":193,"text":8924,"children":10250},[10251,10252,10253],{"id":8927,"depth":251,"text":8927},{"id":8955,"depth":251,"text":8955},{"id":8978,"depth":251,"text":8979},{"id":9003,"depth":193,"text":9003,"children":10255},[10256,10257,10258,10259],{"id":9066,"depth":251,"text":9066},{"id":9095,"depth":251,"text":9095},{"id":9119,"depth":251,"text":9119},{"id":9144,"depth":251,"text":9145},{"id":9157,"depth":193,"text":9157,"children":10261},[10262,10263,10264],{"id":9181,"depth":251,"text":9182},{"id":9198,"depth":251,"text":9199},{"id":9685,"depth":251,"text":9686,"children":10265},[10266,10267,10268],{"id":10123,"depth":259,"text":10124},{"id":10141,"depth":259,"text":10142},{"id":10215,"depth":259,"text":10215},{"id":3865,"depth":193,"text":10235},[1561],"2020-11-03","headlessCMSのstrapi をnuxt.jsで静的書き出しを行う。",{},"\u002Fseries\u002Fheadlesscms-strapi-1",{"title":8782,"description":10272},"headlesscms-strapi","series\u002Fheadlesscms-strapi-1",[2752,5515,10279],"headlesscms","_mix\u002Fnuxt_strapi-768x768.png","AUAh3SzPOmWReCngs8NQezSo25YbFgc0ddNoPwO0euE",{"id":10283,"title":10284,"body":10285,"category":10961,"createdAt":10962,"description":10963,"extension":1564,"index":187,"meta":10964,"navigation":366,"path":10965,"publish":366,"seo":10966,"series":10967,"seriesTitle":10963,"stem":10968,"tag":10969,"thumbnail":10971,"updatedAt":1575,"__hash__":10972},"series\u002Fseries\u002Fconcrete5vue-1.md","Concrete5にVueCLIを使ってUIを構築する。1【環境構築編】",{"type":10,"value":10286,"toc":10948},[10287,10290,10293,10297,10300,10362,10365,10368,10371,10374,10385,10388,10392,10395,10398,10401,10404,10407,10413,10416,10419,10425,10428,10434,10440,10460,10463,10466,10469,10472,10475,10489,10600,10606,10616,10623,10784,10799,10810,10816,10884,10891,10894,10897,10901,10929,10932,10939,10942,10945],[20,10288,10289],{},"こんにちはjunです。今回はconcrete5というCMSでVueを使ったパッケージのUIを作成していこうと思います。今回の記事ではコンポーネントを作成する前の、concrete5にvueプロジェクトを作成してCMSに結びつける環境構築まで行います。",[20,10291,10292],{},"またConcrete5とそのカスタマイズ方法についてある程度熟知している人向け、基準としてはカスタムパッケージを作成したことがある人向けの記事となります。対象とするConcrete5のバージョンは8.4以降となります。",[27,10294,10296],{"id":10295},"なぜvue","なぜVue？",[20,10298,10299],{},"なぜVueを使うのか。それはPHPとjqueryによるUI構築が非常に面倒になったからです。Concrete5にはフォーム（テキストエリアとかCMSからのファイル選択フォーム）をPHPで出力してくれる以下の様なヘルパーが存在します。",[165,10301,10303],{"className":2519,"code":10302,"language":2522,"meta":173,"style":173},"\u002F\u002F ヘルパーを読み込む\n$form = Core::make('helper\u002Fform');\n\n\u002F\u002Fテキストinput\necho $form->text($name, $default_value);\n\n\u002F\u002Fテキストエリア\necho $form-> textarea($name, $default_value);\n\n\u002F\u002F ファイルマネージャーヘルパー\n$file_selector = Core::make('helper\u002Fconcrete\u002Ffile_manager');\necho $file_selector->file('label', 'name_attr', 'Select Photo', $default_fileObj);\n",[138,10304,10305,10310,10315,10319,10324,10329,10333,10338,10343,10347,10352,10357],{"__ignoreMap":173},[184,10306,10307],{"class":186,"line":187},[184,10308,10309],{},"\u002F\u002F ヘルパーを読み込む\n",[184,10311,10312],{"class":186,"line":193},[184,10313,10314],{},"$form = Core::make('helper\u002Fform');\n",[184,10316,10317],{"class":186,"line":251},[184,10318,367],{"emptyLinePlaceholder":366},[184,10320,10321],{"class":186,"line":259},[184,10322,10323],{},"\u002F\u002Fテキストinput\n",[184,10325,10326],{"class":186,"line":271},[184,10327,10328],{},"echo $form->text($name, $default_value);\n",[184,10330,10331],{"class":186,"line":279},[184,10332,367],{"emptyLinePlaceholder":366},[184,10334,10335],{"class":186,"line":4},[184,10336,10337],{},"\u002F\u002Fテキストエリア\n",[184,10339,10340],{"class":186,"line":305},[184,10341,10342],{},"echo $form-> textarea($name, $default_value);\n",[184,10344,10345],{"class":186,"line":313},[184,10346,367],{"emptyLinePlaceholder":366},[184,10348,10349],{"class":186,"line":321},[184,10350,10351],{},"\u002F\u002F ファイルマネージャーヘルパー\n",[184,10353,10354],{"class":186,"line":329},[184,10355,10356],{},"$file_selector = Core::make('helper\u002Fconcrete\u002Ffile_manager');\n",[184,10358,10359],{"class":186,"line":412},[184,10360,10361],{},"echo $file_selector->file('label', 'name_attr', 'Select Photo', $default_fileObj);\n",[20,10363,10364],{},"上記のコードをページ上で実行すれば以下の様にフォームが現れます。",[87,10366],{":src":10367,":width":90},"'_mix\u002Fformhelpertest-768x272.png'",[20,10369,10370],{},"上から順番にテキストインプット、テキストエリア、そしてconcrete５純正のファイルマネージャーが出現します。name属性が与えられ、postを通じてデータを送ることができます。",[20,10372,10373],{},"簡単なブロックやパッケージUIであれば問題ないのですが、",[34,10375,10376,10379,10382],{},[37,10377,10378],{},"フロント側のバリデーションを実装する",[37,10380,10381],{},"決まったフォームを複数生成する",[37,10383,10384],{},"フォーム同士が入力値で依存させる場合（入力値でフォームのパターンが変わる）",[20,10386,10387],{},"上記の際にPHPとjqueryだけでは非常に苦労して工数もかかります。",[47,10389,10391],{"id":10390},"vueを使えばリッチなuiが作成可能","Vueを使えばリッチなUIが作成可能",[20,10393,10394],{},"今流行りのリッチなUI、フォームはほとんどがVue、React、Backbone,jsなどHTMLテンプレート、jsによる状態管理を行うことで簡単に実装できます。工数を抑えつつも、これらのライブラリを用いることでリッチなUIを作成することもできます。",[20,10396,10397],{},"今回の記事ではVue CLIを用いて作成したUIをconcrete5のパッケージ上で表示させ、入力内容の追加・更新・削除まで行える様に実装していきます。原理がわかれば意外と簡単です。",[27,10399,10400],{"id":10400},"パッケージの制作準備",[47,10402,10403],{"id":10403},"パッケージの専用のディレクトリを作成",[20,10405,10406],{},"それではVueで構築したUIを持つパッケージを作成していきましょう。pckageディレクトリ配下にvuetestというカスタムパッケージディレクトリを作ります。またディレクトリ構成は以下の通りにします。",[165,10408,10411],{"className":10409,"code":10410,"language":170},[168],"documentroot $ cd .\u002Fpackage\npackage $ mkdir vuetest && touch ...\npackage $ tree vuetest\n.\u002F\n└── vuetest\n    ├── controller.php\n    ├── controllers\n    │   └── single_page\n    │       └── dashboard\n    │           └── vuetest.php\n    ├── db.xml\n    ├── icon.png\n    ├── js\n    │\n    └── single_pages\n        └── dashboard\n            └── vuetest\n                └── view.php\n",[138,10412,10410],{"__ignoreMap":173},[47,10414,10415],{"id":10415},"vueプロジェクトを作成",[20,10417,10418],{},"パッケージに必要なコントローラーやUIを表示するダッシュボード上のシングルページとそのコントローラーも用意します。そしてjsディレクトリを作成してその中にvue cliを用いてプロジェクトを作成します。",[165,10420,10423],{"className":10421,"code":10422,"language":170},[168],"package $ cd vuetest\u002Fjs\njs $ vue create packageui\n\nVue CLI v4.4.6\n? Please pick a preset: default (babel, eslint) #default\n\nVue CLI v4.4.6\n✨  Creating project in documentroot\u002Fvuetest\u002Fjs\u002Fpackageui.\n🗃  Initializing git repository...\n⚙️  Installing CLI plugins. This might take a while... \n\n🎉  Successfully created project packageui.\n\njs $ cd packageui && tree -L 1\n.\n├── README.md\n├── babel.config.js\n├── node_modules\n├── package-lock.json\n├── package.json\n├── public\n",[138,10424,10422],{"__ignoreMap":173},[20,10426,10427],{},"無事にvueのプロジェクトが作成されました。concrete5との都合によりvue.config.jsファイルを作成してwebpackの設定をいじります。",[165,10429,10432],{"className":10430,"code":10431,"language":170},[168],"packageui $ touch vue.config.js\n",[138,10433,10431],{"__ignoreMap":173},[165,10435,10438],{"className":10436,"code":10437,"language":170},[168],"module.exports = {\n    configureWebpack: {\n      output: {\n        filename: '[name].js',\n        chunkFilename: '[name].js'\n      }\n    },\n  }\n",[138,10439,10437],{"__ignoreMap":173},[20,10441,10442,10443,1267,10445,10448,10449,10452,10453,10456,10457,10459],{},"これは ",[138,10444,6821],{},[138,10446,10447],{},"npm run build --mode development","で",[138,10450,10451],{},".vue","ファイルを",[138,10454,10455],{},".js","ファイルにコンパイルして",[138,10458,5667],{},"配下に配置される時に名前がいつも同じになる様に設定します。",[20,10461,10462],{},"buildをするとビルドされたファイルの名前にはハッシュされた英数字がつくのですが、これがビルドの度にころころ変わります。Concrete5でjsファイルをロードする場合は設定した名前が一致しないと読み込めませんので、ビルドの際に設定を変えなくて済む様に上記の設定をします。",[342,10464,10465],{"id":10465},"もし複数のパッケージで共通のvueファイルを用いる場合",[20,10467,10468],{},"今回は vuetestというパッケージとそのディレクトリ配下に専用のvueプロジェクトを作成しますす。しかし複数のパッケージでvueファイルを使用したい場合、applicationディレクトリにjsディレクトリを作成し、vueプロジェクトを作成してください。",[47,10470,10471],{"id":10471},"コンパイルしたjsファイルをconcrete5と結びつける",[20,10473,10474],{},"vue cliでビルドするとdist配下にコンパイルされたjsファイルが生成されます。そのファイルとレンダー先のconcrete5上のページ（シングルページ）で読み込める様に、このjsファイルをCMSが読み込める様に設定します。",[20,10476,10477,10478,10480,10481,10484,10485,10488],{},"まずとりあえず以下の",[138,10479,6300],{},"をビルドしてみます。レンダリング先に ",[138,10482,10483],{},"id=\"app\""," という要素があれば",[138,10486,10487],{},"Appコンポーネント","がレンダーされます。",[165,10490,10492],{"className":6503,"code":10491,"filename":6505,"language":6506,"meta":173,"style":173},"import Vue from 'vue'\nimport App from '.\u002FApp.vue'\n\nVue.config.productionTip = false\n\nnew Vue({\n  render: h => h(App),\n}).$mount('#app')\n",[138,10493,10494,10508,10524,10528,10545,10549,10560,10580],{"__ignoreMap":173},[184,10495,10496,10498,10500,10502,10504,10506],{"class":186,"line":187},[184,10497,2819],{"class":2759},[184,10499,3246],{"class":1056},[184,10501,2825],{"class":2759},[184,10503,233],{"class":229},[184,10505,2701],{"class":236},[184,10507,240],{"class":229},[184,10509,10510,10512,10515,10517,10519,10522],{"class":186,"line":193},[184,10511,2819],{"class":2759},[184,10513,10514],{"class":1056}," App ",[184,10516,2825],{"class":2759},[184,10518,233],{"class":229},[184,10520,10521],{"class":236},".\u002FApp.vue",[184,10523,240],{"class":229},[184,10525,10526],{"class":186,"line":251},[184,10527,367],{"emptyLinePlaceholder":366},[184,10529,10530,10532,10534,10536,10538,10541,10543],{"class":186,"line":259},[184,10531,3301],{"class":1056},[184,10533,1304],{"class":229},[184,10535,9387],{"class":1056},[184,10537,1304],{"class":229},[184,10539,10540],{"class":1056},"productionTip ",[184,10542,1033],{"class":229},[184,10544,5558],{"class":5557},[184,10546,10547],{"class":186,"line":271},[184,10548,367],{"emptyLinePlaceholder":366},[184,10550,10551,10554,10556,10558],{"class":186,"line":279},[184,10552,10553],{"class":229},"new",[184,10555,3444],{"class":1253},[184,10557,1257],{"class":1056},[184,10559,3449],{"class":229},[184,10561,10562,10565,10567,10570,10573,10575,10578],{"class":186,"line":4},[184,10563,10564],{"class":1253},"  render",[184,10566,230],{"class":229},[184,10568,10569],{"class":1285}," h",[184,10571,10572],{"class":993}," =>",[184,10574,10569],{"class":1253},[184,10576,10577],{"class":1056},"(App)",[184,10579,3457],{"class":229},[184,10581,10582,10584,10586,10588,10590,10592,10594,10596,10598],{"class":186,"line":305},[184,10583,3423],{"class":229},[184,10585,1294],{"class":1056},[184,10587,1304],{"class":229},[184,10589,3475],{"class":1253},[184,10591,1257],{"class":1056},[184,10593,1260],{"class":229},[184,10595,3482],{"class":236},[184,10597,1260],{"class":229},[184,10599,3426],{"class":1056},[165,10601,10604],{"className":10602,"code":10603,"language":170},[168],"packageui ＄ npm run build\n.\n├── README.md\n├── babel.config.js\n├── dist\n│   ├── app.js\n│   ├── app.js.map\n│   ├── chunk-vendors.js\n│   ├── chunk-vendors.js.map\n│   ├── css\n│   ├── favicon.ico\n│   ├── img\n│   └── index.html\n├\n",[138,10605,10603],{"__ignoreMap":173},[20,10607,10608,10609,159,10612,10615],{},"ビルド成功。いらないものもありますが、",[138,10610,10611],{},"app.js",[138,10613,10614],{},"chunk-vendors.js","が読み込まれる様にすればOKです。",[20,10617,10618,10619,10622],{},"パッケージ専用のjsファイルで作成する場合はパッケージのインストールコントローラ（",[138,10620,10621],{},"package\u002Fvuetest\u002Fcontroller.php","）に以下の様な記述をします。",[165,10624,10627],{"className":2519,"code":10625,"filename":10626,"language":2522,"meta":173,"style":173},"\u003C?php\nnamespace Concrete\\Package\\Vuetest;\ndefined('C5_EXECUTE') or die('Access Denied.');\nuse \\Concrete\\Core\\Asset\\AssetList;\nuse \\Concrete\\Core\\Asset\\Asset;\n\nclass Controller extends \\Concrete\\Core\\Package\\Package {\n    protected $pkgHandle = 'vuetest';\n    protected $appVersionRequired = '5.7.4';\n    protected $pkgVersion = '1.0.0';\n\n    public function on_start()\n    {\n        $al = AssetList::getInstance();\n        $al->register(\n            'javascript', 'package-vue-build', 'js\u002Fpackageui\u002Fdist\u002Fapp.js',\n            array('version' => '1.0.0', 'position' => Asset::ASSET_POSITION_FOOTER, 'combine' => true),\n            $this->pkgHandle\n        );\n        \n        $al->register(\n            'javascript', 'package-vue-chunk', 'js\u002Fpackageui\u002Fdist\u002Fchunk-vendors.js',\n            array('version' => '1.0.0', 'position' => Asset::ASSET_POSITION_FOOTER, 'combine' => true),\n            $this->pkgHandle\n        );\n        \n        $al->registerGroup('package-vue-production', array(\n            array('javascript', 'package-vue-build'),\n            array('javascript', 'package-vue-chunk'),\n        )); \n    }\n...\n}\n","controller.php",[138,10628,10629,10634,10639,10644,10649,10654,10658,10663,10668,10673,10678,10682,10687,10692,10697,10702,10707,10712,10717,10722,10727,10731,10736,10740,10744,10748,10752,10757,10762,10767,10772,10776,10780],{"__ignoreMap":173},[184,10630,10631],{"class":186,"line":187},[184,10632,10633],{},"\u003C?php\n",[184,10635,10636],{"class":186,"line":193},[184,10637,10638],{},"namespace Concrete\\Package\\Vuetest;\n",[184,10640,10641],{"class":186,"line":251},[184,10642,10643],{},"defined('C5_EXECUTE') or die('Access Denied.');\n",[184,10645,10646],{"class":186,"line":259},[184,10647,10648],{},"use \\Concrete\\Core\\Asset\\AssetList;\n",[184,10650,10651],{"class":186,"line":271},[184,10652,10653],{},"use \\Concrete\\Core\\Asset\\Asset;\n",[184,10655,10656],{"class":186,"line":279},[184,10657,367],{"emptyLinePlaceholder":366},[184,10659,10660],{"class":186,"line":4},[184,10661,10662],{},"class Controller extends \\Concrete\\Core\\Package\\Package {\n",[184,10664,10665],{"class":186,"line":305},[184,10666,10667],{},"    protected $pkgHandle = 'vuetest';\n",[184,10669,10670],{"class":186,"line":313},[184,10671,10672],{},"    protected $appVersionRequired = '5.7.4';\n",[184,10674,10675],{"class":186,"line":321},[184,10676,10677],{},"    protected $pkgVersion = '1.0.0';\n",[184,10679,10680],{"class":186,"line":329},[184,10681,367],{"emptyLinePlaceholder":366},[184,10683,10684],{"class":186,"line":412},[184,10685,10686],{},"    public function on_start()\n",[184,10688,10689],{"class":186,"line":417},[184,10690,10691],{},"    {\n",[184,10693,10694],{"class":186,"line":423},[184,10695,10696],{},"        $al = AssetList::getInstance();\n",[184,10698,10699],{"class":186,"line":429},[184,10700,10701],{},"        $al->register(\n",[184,10703,10704],{"class":186,"line":435},[184,10705,10706],{},"            'javascript', 'package-vue-build', 'js\u002Fpackageui\u002Fdist\u002Fapp.js',\n",[184,10708,10709],{"class":186,"line":441},[184,10710,10711],{},"            array('version' => '1.0.0', 'position' => Asset::ASSET_POSITION_FOOTER, 'combine' => true),\n",[184,10713,10714],{"class":186,"line":447},[184,10715,10716],{},"            $this->pkgHandle\n",[184,10718,10719],{"class":186,"line":453},[184,10720,10721],{},"        );\n",[184,10723,10724],{"class":186,"line":459},[184,10725,10726],{},"        \n",[184,10728,10729],{"class":186,"line":464},[184,10730,10701],{},[184,10732,10733],{"class":186,"line":470},[184,10734,10735],{},"            'javascript', 'package-vue-chunk', 'js\u002Fpackageui\u002Fdist\u002Fchunk-vendors.js',\n",[184,10737,10738],{"class":186,"line":476},[184,10739,10711],{},[184,10741,10742],{"class":186,"line":481},[184,10743,10716],{},[184,10745,10746],{"class":186,"line":487},[184,10747,10721],{},[184,10749,10750],{"class":186,"line":493},[184,10751,10726],{},[184,10753,10754],{"class":186,"line":499},[184,10755,10756],{},"        $al->registerGroup('package-vue-production', array(\n",[184,10758,10759],{"class":186,"line":505},[184,10760,10761],{},"            array('javascript', 'package-vue-build'),\n",[184,10763,10764],{"class":186,"line":511},[184,10765,10766],{},"            array('javascript', 'package-vue-chunk'),\n",[184,10768,10769],{"class":186,"line":517},[184,10770,10771],{},"        )); \n",[184,10773,10774],{"class":186,"line":523},[184,10775,520],{},[184,10777,10778],{"class":186,"line":528},[184,10779,8466],{},[184,10781,10782],{"class":186,"line":533},[184,10783,400],{},[20,10785,10786,10787,10790,10791,10794,10795,10798],{},"vuetestパッケージのコントローラーで",[138,10788,10789],{},"$al->register","を用いて、パスで指定したjsファイルを登録します。２つあるのでを",[138,10792,10793],{},"$al->registerGroup","用いてのp",[138,10796,10797],{},"ackage-vue-production","名前で２つのjsファイルを読み込む様にグルーピングします。",[20,10800,10801,10802,10805,10806,10809],{},"concrete5ではこの様なjs\u002Fcssのアセット登録システムがあり、登録をすれば適当にアセットが読み込むことができる様になります。",[68,10803,10804],{},"パッケージのコントローラーでは登録をした"," ので、次はjsファイルが必要な",[68,10807,10808],{},"ページのコントローラーで呼び出し"," を行います。",[20,10811,10812,10815],{},[138,10813,10814],{},"vuetest\u002Fsingle_pages\u002Fdashboard\u002Fvuetest\u002Fview.php"," に以下の様に記述します。",[165,10817,10819],{"className":2519,"code":10818,"language":2522,"meta":173,"style":173},"\u003C?php\nnamespace Concrete\\Package\\Vuetest\\Controller\\SinglePage\\Dashboard;\ndefined('C5_EXECUTE') or die('Access Denied.');\nuse \\Concrete\\Core\\Page\\Controller\\DashboardPageController;\n\nclass Vuetest extends DashboardPageController\n{\n    public $packageHandle = 'vuetest';\n\n    public function view() {\n        $this->requireAsset('package-vue-production');\n        $this->set('success', 'My success message');\n    }\n}\n",[138,10820,10821,10825,10830,10834,10839,10843,10848,10852,10857,10861,10866,10871,10876,10880],{"__ignoreMap":173},[184,10822,10823],{"class":186,"line":187},[184,10824,10633],{},[184,10826,10827],{"class":186,"line":193},[184,10828,10829],{},"namespace Concrete\\Package\\Vuetest\\Controller\\SinglePage\\Dashboard;\n",[184,10831,10832],{"class":186,"line":251},[184,10833,10643],{},[184,10835,10836],{"class":186,"line":259},[184,10837,10838],{},"use \\Concrete\\Core\\Page\\Controller\\DashboardPageController;\n",[184,10840,10841],{"class":186,"line":271},[184,10842,367],{"emptyLinePlaceholder":366},[184,10844,10845],{"class":186,"line":279},[184,10846,10847],{},"class Vuetest extends DashboardPageController\n",[184,10849,10850],{"class":186,"line":4},[184,10851,3449],{},[184,10853,10854],{"class":186,"line":305},[184,10855,10856],{},"    public $packageHandle = 'vuetest';\n",[184,10858,10859],{"class":186,"line":313},[184,10860,367],{"emptyLinePlaceholder":366},[184,10862,10863],{"class":186,"line":321},[184,10864,10865],{},"    public function view() {\n",[184,10867,10868],{"class":186,"line":329},[184,10869,10870],{},"        $this->requireAsset('package-vue-production');\n",[184,10872,10873],{"class":186,"line":412},[184,10874,10875],{},"        $this->set('success', 'My success message');\n",[184,10877,10878],{"class":186,"line":417},[184,10879,520],{},[184,10881,10882],{"class":186,"line":423},[184,10883,400],{},[20,10885,10886,10887,10890],{},"この",[138,10888,10889],{},"$this->requireAsset('package-vue-production');","で「先ほど登録したjsアセットをこのページで読み込め！」と命令しています。設定した後、実際に該当ページで探してみましょう。",[87,10892],{":src":10893,":width":90},"'_mix\u002Fsc-2020-08-01-19.52.09-768x133.png'",[20,10895,10896],{},"いました。\u002Fpackages\u002Fvuetest\u002Fjs\u002Fdist\u002F** と指定したパスにて読み込まれています。それではid=\"app\"を持つ適当なdivを作成して再度みてみましょう。",[20,10898,10899,3810],{},[138,10900,10814],{},[165,10902,10905],{"className":2519,"code":10903,"filename":10904,"language":2522,"meta":173,"style":173},"\u003C?php\ndefined('C5_EXECUTE') or die('Access Denied.');\n?>\n\n\u003Cdiv id=\"app\">\u003C\u002Fdiv>\n","view.php",[138,10906,10907,10911,10915,10920,10924],{"__ignoreMap":173},[184,10908,10909],{"class":186,"line":187},[184,10910,10633],{},[184,10912,10913],{"class":186,"line":193},[184,10914,10643],{},[184,10916,10917],{"class":186,"line":251},[184,10918,10919],{},"?>\n",[184,10921,10922],{"class":186,"line":259},[184,10923,367],{"emptyLinePlaceholder":366},[184,10925,10926],{"class":186,"line":271},[184,10927,10928],{},"\u003Cdiv id=\"app\">\u003C\u002Fdiv>\n",[87,10930],{":src":10931,":width":6845,":center":1446},"'_mix\u002Fsh-2020-08-01-19.56.01-768x812.png'",[20,10933,10934,10935,10938],{},"パスの関係上レイアウトが崩れ、画像が読み込めていませんがvue側で記述した内容が無事にレンダリングされています。これでvueとconcrete5のセッティングは完了です。vueプロジェクトでコンポーネントを作成しながら、適切な",[138,10936,10937],{},"id","を用いてページ上にレンダーします。",[27,10940,10941],{"id":10941},"次はフォームコンポーネントの作成",[20,10943,10944],{},"以上がconcrete5のパッケージ上にvueプロジェクトを作成して、concrete5に結びつける方法でした。これでvueを使え、レンダーされる環境は整ったので次は情報を登録するための登録フォームと編集画面の作成を行っていきます。",[1530,10946,10947],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s6cf3, html code.shiki .s6cf3{--shiki-default:#89DDFF;--shiki-default-font-style:italic}html pre.shiki code .s0W1g, html code.shiki .s0W1g{--shiki-default:#BABED8}html pre.shiki code .sAklC, html code.shiki .sAklC{--shiki-default:#89DDFF}html pre.shiki code .sfyAc, html code.shiki .sfyAc{--shiki-default:#C3E88D}html pre.shiki code .sbqyR, html code.shiki .sbqyR{--shiki-default:#FF9CAC}html pre.shiki code .sdLwU, html code.shiki .sdLwU{--shiki-default:#82AAFF}html pre.shiki code .s7ZW3, html code.shiki .s7ZW3{--shiki-default:#BABED8;--shiki-default-font-style:italic}html pre.shiki code .sJ14y, html code.shiki .sJ14y{--shiki-default:#C792EA}",{"title":173,"searchDepth":251,"depth":251,"links":10949},[10950,10953,10960],{"id":10295,"depth":193,"text":10296,"children":10951},[10952],{"id":10390,"depth":251,"text":10391},{"id":10400,"depth":193,"text":10400,"children":10954},[10955,10956,10959],{"id":10403,"depth":251,"text":10403},{"id":10415,"depth":251,"text":10415,"children":10957},[10958],{"id":10465,"depth":259,"text":10465},{"id":10471,"depth":251,"text":10471},{"id":10941,"depth":193,"text":10941},[1561],"2020-08-25","Concrete5にVueCLIを使ってUIを構築する。",{},"\u002Fseries\u002Fconcrete5vue-1",{"title":10284,"description":10963},"concrete5vue","series\u002Fconcrete5vue-1",[10970,2752,2701],"concrete5","_mix\u002Fvuewithconcrete.png","SFdt3_VrU2GVgYjfNjC32zyOKCtr79IKO3992JqFQJo",1780987151341]