반응형

1. 라이브러리 설치

 yarn add @sanity/block-content-to-react
 yarn add react-syntax-highlighter

 

2. BlogPostDetail.jsx

import {Col, Row } from 'antd'
import BlockContent from '@sanity/block-content-to-react'
import SyntaxHighlighter from 'react-syntax-highlighter'

const serializers = {
  types: {
    code: ({node}) => {
      const { code } = node;
      return (
        <SyntaxHighlighter 
          language="javascript" 
          style={{"hljs": {color: 'red',}}}
        >{code}</SyntaxHighlighter>
      )
    },
    video: ({ node }) => {
      return <p>video</p>
    },
    link: ({ node }) => {
      return <p>link</p>
    },
    imageGallery: ({ node }) => {
      return <p>imageGallery</p>
    },
  }
}

export default function BlogPostDetail({blocks}) {
  return (
    <>
      <Col span={24}>
        <BlockContent 
          blocks={blocks}
          projectId= {process.env.SANITY_PROJECT_ID}
          dataset="production"  
          serializers={serializers}
        />
      </Col>
    </>
  )
}
반응형
반응형

1. 라이브러리 설치

yarn add antd @ant-design/icons		// ant design 
yarn add dayjs				// 날짜 변환 라이브러리

2. pages/_app.js 파일에 반영 -> 전체 적용

import 'antd/dist/antd.css' //<- 추가
import '../styles/globals.css'

function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />
}

export default MyApp

3. Roboto 적용을 위한 /pages/_document.js 파일 생성

참고문서 경로: https://nextjs.org/docs/advanced-features/custom-document

import Document, { Html, Head, Main, NextScript } from 'next/document'

class MyDocument extends Document {
  static async getInitialProps(ctx) {
    const initialProps = await Document.getInitialProps(ctx)
    return { ...initialProps }
  }

  render() {
    return (
      <Html>
        <Head>
          {/* 구글 웹폰트 Roboto 링크 추가 */}
          <link rel="preconnect" href="https://fonts.gstatic.com" />
          <link href="https://fonts.googleapis.com/css2?family=Roboto&display=swap" rel="stylesheet" />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    )
  }
}

export default MyDocument

 

4. styles/globals.css 파일에서 적용 폰트 삭제 및 수정

html,
body {
  padding: 0;
  margin: 0;
  /* 아래부분 수정 */
  font-family: Roboto, sans-serif;
}

a {
  color: inherit;
  text-decoration: none;
}

* {
  box-sizing: border-box;
}

5. styles/Home.module.css 파일에서 .container 클래스 수정

.container {
  width: 1024px;
  margin-left: auto;
  margin-right: auto;
}

6. pages/index.js

첫번째로  Header 컴포넌트를 가져오고, 추후 만들어지는 컴포넌트를 계속 추가해주면 됩니다.

import styles from '../styles/Home.module.css';
import SanityService from '../services/SanityService';
import { Col, Row } from 'antd';
import Link from 'next/link'
import { CodeOutlined } from '@ant-design/icons'
import Header from '../components/Header';

export default function Home({home, posts}) {  
  const mainPost = posts.find(p => p.slug === home.mainPostUrl);
  const otherPosts = posts.filter(p => p.slug !== home.mainPostUrl);
  console.log(mainPost);
  console.log(otherPosts);
  return (
    <div className={styles.container}>
      <Header />
    </div>
  )
}

export async function getStaticProps() {  
  const sanityService = new SanityService();
  const home = await sanityService.getHome();
  const posts = await sanityService.getPosts();

  return {
    props: {
      home,
      posts,
    }
  }
}

7. 컴포넌트 작성 (/components/Header.jsx)

/* eslint-disable react/no-unescaped-entities */
import { Row, Col } from 'antd';
import { CodeOutlined } from '@ant-design/icons'
import Link from 'next/link'


 const Header = () => {
  return (
      <Row
        align="middle"
        style={{
          height: 64,
        }}
      >
        <Col span={24}>
          <Link href="/">
            <a>
              <div
                style={{
                  fontSize: 20,
                  fontWeight: 'bold',
                }}>
                <CodeOutlined />Willy's Blog
              </div>
            </a>
          </Link>
        </Col>
      </Row>
  );
};

export default Header;

8. 결과

반응형
반응형

1. 프로젝트 생성 및 Library 설치

yarn create next-app .
yarn add @sanity/client	//Sanity 연결하여 Data를 가져오기 위한 라이브러리

2. 시작

yarn dev

yarn start는 빌드 이후 가능합니다.

 

3. 라우팅 설정

(기초)

폴더/페이지구조					url
pages/index.js 				-> 	/
pages/blog/index.js			-> 	/blog
pages/blog/first-post.js		->	/blog/first-post
pages/dashboard/setting/username.js	->	/dashboard/settings/username

pages/blog/[slug].js			->	/blog/:slug	(/blog/hello-world)
pages/[username]/settings.js		->	/:username/settings (/foo/settings)
pages/post/[...all].js			->	/post/* (/post/2020/id/title)

static routing이 우선됩니다.

 

(pages/post/index.js)

import React from 'react';

const Blog = () => {
  return (
    <div>
      <h1>Blog</h1>
    </div>
  );
};

export default Blog;

(pages/post/[slug].js)

import React from 'react';
import { useRouter } from 'next/router';

const Blog = () => {
  const router = useRouter();

  const { slug } = router.query;

  return (
    <div>
      <h1>blog/{slug}</h1>
    </div>
  );
};

export default Blog;

 

3. Sanity 연결 및  Data 가져오기

getStaticProps (Static Generation) - Fetch data at build time

getStaticPaths (Static Generation) - Specify dynamic routes to pre-render pages based on data.

getServerSideProps (Server-side Rendering) - Fetch data on each request

 

(/services/SanityService.js) sanity 연결 및 데이터를 요청하는 메서드(클래스)를 정의합니다.

import sanityClient from '@sanity/client';

export default class SanityService {
  _client = sanityClient({
    dataset: 'production',
    projectId: 'ozi5ivc6',  // sanity.io에 접속하여 프로젝트의 ID를 확인한다.
    useCdn: process.env.NODE_ENV === 'production',  // production 모드일 경우 cdn 사용
  });

  // main post를 가져오는 요청
  async getHome() {    
    return await this._client.fetch(`
      *[_type =='home'][0]{'mainPostUrl': mainPost -> slug.current}
    `)
  }

  // main 이외의 포스트를 가져오는 요청
  async getPosts() {
    return await this._client.fetch(`
      *[_type=='post']{
        title, 
        subtitle, 
        createdAt, 
        'content': content[]{
          ..., 
          ...select(_type == 'imageGallery' => {'images': images[]{..., 'url': asset -> url }} )
        },
        'slug': slug.current,
        'thumbnail': {
          'alt': thumbnail.alt,
          'imageUrl': thumbnail.asset -> url
        },
        'author': author -> {
          name,
          role,
          'image': image.asset -> url
        },
        'tag': tag -> {
          title,
          'slug': slug.current
        }
      }
    `)
  }
}

 

(/pages/index.js) 루트 경로에서 메인 포스트와 기타 포스트들을 가져옵니다.

import styles from '../styles/Home.module.css';
import SanityService from '../services/SanityService';

export default function Home({home, posts}) {
  console.log(home);
  console.log(posts);
  return (
    <div className={styles.container}>
      <h1>Blog Home</h1>
    </div>
  )
}

export async function getStaticProps() {  
  const sanityService = new SanityService();
  const home = await sanityService.getHome();
  const posts = await sanityService.getPosts();

  return {
    props: {
      home,
      posts,
    }
  }
}

(/pages/post/[slug].js) 특정 포스트(slug)의 데이터를 가져옵니다.

import React from 'react';
import SanityService from '../../services/SanityService';

const PostAll = ({slug, post}) => {
  return (
    <div>
      <h1>blog/{slug}</h1>
    </div>
  );
};

export default PostAll;

export async function getStaticPaths() {
   
  const posts = await new SanityService().getPosts();

  const paths = posts.map(post => ({
    params: {
      slug: post.slug
    }
  }));

  return {
    paths,
    fallback: false,
  };
}

export async function getStaticProps({params}) {
  const {slug} = params;

  const posts = await new SanityService().getPosts();
  const post = posts.find(p => p.slug === slug);

  return {
    props: {
      slug,
      post,
    }
  }
}

 

4. 결과

/localhost:3000/post/my-blog-test

 

/localhost:3000/

 

반응형
반응형

보안을 위해 API Key가 노출되지 않도록 API 요청 부분을 서버리스 함수로 작성하도록 하겠습니다.

추가로 API Key는 환경변수로 등록하여 Github 등으로 올릴 때 노출되는 것을 막기위해 추가적으로 보안설정을 하겠습니다.

 

1.netlify-cli 설치

npm i -D netlify-cli

2.netlify.toml

# Netlify Dev
# https://cli.netlify.com/netlify-dev/#netlifytoml-dev-block

# 제품 모드
[build]
  command = "npm run build"
  functions = "functions" # Netlify 서버리스 함수가 작성된 디렉토리를 지정합니다.
  publish = "build" # 프로젝트 빌드 결과의 디렉토리를 지정합니다.

# 개발 모드
[dev]
  framework = "#custom" # 감지할 프로젝트 유형을 지정합니다. 앱 서버 및 `targetPort` 옵션을 실행하는 명령 옵션은 `#custom`입니다.
  command = "npm run dev" # 연결할 프로젝트의 개발 서버를 실행하는 명령(스크립트)을 지정합니다.
  targetPort = 8080 # 연결할 프로젝트 개발 서버의 포트를 지정합니다.
  port = 8888 # 출력할 Netlify 서버의 포트를 지정합니다.
  publish = "public" # 프로젝트의 정적 콘텐츠 디렉토리를 지정합니다.
  jwtRolePath = "app_metadata.authorization.roles" # JWT 기반 리디렉션에 대한 역할 값을 찾아야하는 객체 경로를 지정합니다.
  autoLaunch = true # Netlify 서버가 준비되면 자동으로 브라우저를 오픈할 것인지 지정합니다.


3. package.json

 "scripts": {
    "dev": "snowpack dev",
    "dev:netlify": "netlify dev",
    "build": "snowpack build"
  },

4. /functions/movie.js

Netlify에서 작동할 서버리스 함수입니다. Netlify에서는 /functions/movie.js에 함수를 작성하게 되면, 'http://localhost:8079/.netlify/functions/movie' 와 같이 요청할 수 있습니다. node.js 환경에서 작동하므로 require로 모듈을 가져와야합니다.

const axios = require('axios')
const { OMDB_API_KEY } = process.env

exports.handler = async function(event, context) {
  const params = JSON.parse(event.body)
  const { title, type, year, page, id } = params
  //const OMDB_API_KEY='abd6b67a'

  const url = id 
    ? `https://www.omdbapi.com/?apikey=${OMDB_API_KEY}&i=${id}&plot=full` 
    : `https://www.omdbapi.com/?apikey=${OMDB_API_KEY}&s=${title}&type=${type}&y=${year}&page=${page}`

  try{
    const res = await axios.get(url)
    console.log(res.data)
    if (res.data.Error) { //OMDB의 특수한 경우(정상반환되었으나 내용에 에러가 포함됨) 처리
      //reject(res.data.Error)
      return {
        statusCode: 400,
        body: res.data.Error
      }
    }
    //resolve(res)
    return {
      statusCode: 200,
      body: JSON.stringify(res.data)
    }
  } catch(error) {
    console.log(error.response.status)
    //reject(error.message)
    return {
      statusCode: error.response.status,
      body: error.message
    }
  }
}

 

5. 테스트용 app.svelte 소스

<script>
  import { fade } from 'svelte/transition'
  import Router,{ location } from 'svelte-spa-router'  
  import routes from '~/routes/index.js'
  import Header from '~/components/Header.svelte'  
  import Footer from '~/components/Footer.svelte'
  
  import axios from 'axios'

  async function test() {
    const res = await axios.get('/.netlify/functions/test')
    console.log(res)
  }
  test()
</script>

<Header />
{#key $location}
  <div in:fade>
    <Router 
      routes={routes} 
      restoreScrollState={true}/>
  </div>
{/key}
<Footer />

6. Netlify 환경변수에  OMDB_API_KEY 설정

경로: Netlify -> Site settings -> Build & deploy -> Environment -> Environment variables -> Edit Variables

 

7. 로컬서버에서도 테스트 가능하도록 환경변수 설정( .env 파일 생성)

OMDB_API_KEY=7035c60c

(주의) .gitignore에 추가하여 깃헙에 업로드되지 않도록 한다.

 

8. 추후 유지보수를 위한 설정 변경

1) package.json 시작 스크립트 변경

"scripts": {
    "dev:snowpack": "snowpack dev",
    "dev": "netlify dev",
    "build": "snowpack build"
  },

2) netlify.toml파일 변경

command를 바꿔주고, 시작 포트를 8079로 변경한다.

# 개발 모드
[dev]
  framework = "#custom" # 감지할 프로젝트 유형을 지정합니다. 앱 서버 및 `targetPort` 옵션을 실행하는 명령 옵션은 `#custom`입니다.
  command = "npm run dev:snowpack" # 연결할 프로젝트의 개발 서버를 실행하는 명령(스크립트)을 지정합니다.
  targetPort = 8079 # 연결할 프로젝트 개발 서버의 포트를 지정합니다.
  port = 8080 # 출력할 Netlify 서버의 포트를 지정합니다.
  publish = "public" # 프로젝트의 정적 콘텐츠 디렉토리를 지정합니다.
  jwtRolePath = "app_metadata.authorization.roles" # JWT 기반 리디렉션에 대한 역할 값을 찾아야하는 객체 경로를 지정합니다.
  autoLaunch = true # Netlify 서버가 준비되면 자동으로 브라우저를 오픈할 것인지 지정합니다.

3) snowpack.config.js 파일의 시작 포트 변경

module.exports = {
  mount: {
    public: '/',
    src: '/_dist_'
  },
  plugins: [
    '@snowpack/plugin-svelte',
    ['@snowpack/plugin-babel', {
      transformOptions: babelOptions()
    }],
    '@snowpack/plugin-dotenv',
    '@snowpack/plugin-optimize'
  ],
  alias: {
    '~': './src'
  },
  devOptions: {
    port: 8079
  }
}
반응형
반응형

1. 백엔드

헤로쿠에 백엔드를 배포할 경우 CORS(Cross Origin Resource Sharing) 문제를 해결하기 위해 라이브러리를 설치해야 합니다. Koa로 작업할 경우 'koa2-cors' 를 설치해주면 됩니다.

그리고 라우팅 설정 부분에서 아래의 구문을 추가해줍니다.

import cors from 'koa2-cors';

app.use(cors());

 

2. 프론트엔드

Github의 소스를 자동으로 배포해주는 Netlify의 경우에도 일부 수정할 부분이 필요합니다. Local에서 작업할 경우에는 같은 localhost에서 데이터를 주고 받아서 몰랐었는데, 이제 헤로쿠의 백엔드 데이터를 받아와야 하기 때문에 조금 달라져야 합니다. 그래서 필요한 것이 _redirects파일 입니다. /public 폴더에 _redirects라는 이름의 파일을 생성한 후 아래와 같이 작성해줍니다. 

/*  /index.html 200
/api/* https://헤로쿠앱이름.herokuapp.com/api/:splat  200

프론트엔드 화면에서 버튼이나 페이지 이동에 의해 서버 요청이 발생할 경우, url에 해당하는 경로를 적고, 그에 대응하는 실제 주소를 적어주면 됩니다. 뒤의 :splat은 *과 동일한 의미라고 합니다. 그리고 뒤의 숫자 200은 아직 잘 모르겠습니다. 정상 접속을 의미하는 200이라면 굳이 붙여야 하는지도 잘 모르겠고... 다른 숫자를 붙일 수도 있는데, 301은 url을 영구적으로 옮겼을 때 사용하고, 검색엔진 최적화에 좋다고 합니다. 302 일시적으로 옮겼을 때 사용한다고 합니다.

 

~끝~

반응형
반응형

 

무작정 따라한 소스를 기록으로 남김니다.

 

1. App.js

RecoilRoot 를 사용하여 TodoList 요소를 감싸준다.

import React from 'react';
import TodoList from './components/TodoList'
import { RecoilRoot } from 'recoil';



function App() {
  return (
    <div>
      <RecoilRoot>
        <TodoList />
      </RecoilRoot>
    </div>
  );
}

export default App;

 

2. ./src/recoil/state.js 작성

import { atom } from 'recoil';

export const todoListState = atom({
  key: 'todoListState',
  default: [],
});

3. ./src/components/TodoList.jsx 작성

import React from 'react';
import { useRecoilValue } from 'recoil';

import TodoItemCreator from './TodoItemCreator';
import TodoItem from './TodoItem';
import { todoListState } from '../recoil/state';

const TodoList = () => {
  const todoList = useRecoilValue(todoListState);
  return (
    <div>
      <TodoItemCreator />

      {todoList.map((todoItem) => (
        <TodoItem key={todoItem.id} item={todoItem} />
      ))}
    </div>
  );
};

export default TodoList;

4. ./src/components/TodoItemCreator.jsx 작성

import React, { useState } from 'react';
import { todoListState } from '../recoil/state';
import { useSetRecoilState } from 'recoil';

const TodoItemCreator = () => {
  const [inputValue, setInputValue] = useState('');
  const setTodoList = useSetRecoilState(todoListState);

  const addItem = () => {
    setTodoList((oldTodoList) => {
      const id = oldTodoList.length
      ? oldTodoList[oldTodoList.length - 1].id + 1
      : 0;
      return [
        ...oldTodoList,
        {
          id,
          text: inputValue,
          isComplete: false,
        },
      ];
    });
    setInputValue('');
  };

  const onChange = ({ target: { value } }) => {
    setInputValue(value);
  };

  return (
    <div>
      <input type='text' value={inputValue} onChange={onChange} />
      <button onClick={addItem}>Add</button>
    </div>
  );
};

export default TodoItemCreator;

5. ./src/components/TodoItem.jsx 작성

import React from 'react';
import { useRecoilState } from 'recoil';
import { todoListState } from '../recoil/state';

const TodoItem = ({item}) => {
  const [todoList, setTodoList] = useRecoilState(todoListState);

  const editItemText = ({ target: { value }}) => {
    const newList = todoList.map((listItem) =>
    listItem.id === item.id ? {...listItem, text: value } : listItem
    );
    setTodoList(newList);
  };

  const ToggleItemCompletion = () => {
    const newList = todoList.map((listItem) => 
    listItem.id ===item.id
    ? {...listItem, isComplete: !item.isComplete }
    : listItem
    );
    setTodoList(newList);
  };

  const deleteItem = () => {
    const newList = todoList.filter((listItem) => listItem.id !== item.id);
    setTodoList(newList);
  };
  return (
    <div>
      <input type="text" value={item.text} onChange={editItemText} />
      <input 
        type="checkbox" 
        checked={item.isComplete} 
        onChange={ToggleItemCompletion} 
      />
      <button onClick={deleteItem}>X</button>
    </div>
  );
};

export default TodoItem;

 

6. 결과

yart start

반응형
반응형

 

1. 프로젝트 구성 (Template Download)

  /node_modules

  .babelrc.js

  .gitignore

  .postcssrc.js

  .babelrc.js

  .package-lock.json

  .package.json

  .webpack.config.js

 

  index.html

  /src/App.vue

  /src/main.js

  /src/assets/logo.jpg

    ㄴ/components/HelloWorld.vue

  /scss/main.scss

  /static/fabicon.ico

 

2. Package 설치

   npm i vue@next

   npm i -D vue-loader@next vue-style-loader @vue/compiler-sfc

   npm i -D file-loader

   npm i -D eslint eslint-plugin-vue babel-eslint

 

3. webpack.config.js 수정

const path = require('path')
const HtmlPlugin = require('html-webpack-plugin')
const CopyPlugin = require('copy-webpack-plugin')
const { VueLoaderPlugin } = require('vue-loader') // 추가

module.exports = {
    resolve: {
        extensions: ['.js', '.vue'], // 추가: vue 확장자 자동 인식
        alias: {
            '~': path.resolve(__dirname, 'src'),
            'assets': path.resolve(__dirname, 'src/assets')
        }
    }, 
    
	// 파일을 읽어들이기 시작하는 진입점 설정
    entry: './src/main.js',	// 엔트리 파일 인식. 번들링 결과물에 포함시킴
    output: {
    	// 현재는 path와 filename이 기본값과 동일하므로 생략 가능
    	// path: path.resolve(__dirname, 'dist'),	//절대경로 필요, 폴더명
        // filename: 'main.js',
        clean: true	// 새로 build 시 기존 필요없는 파일/폴더 삭제
    },
    
   module: {
        rules: [
            {
                test: /\.vue$/,     // 추가
                use: 'vue-loader'   // 추가
            },
            {
                test: /\.s?css$/,	// .css 또는 scss로 끝나는 파일 인식
                use: [
                    'vue-style-loader', // 추가
                    'style-loader',	// html에 삽입해주는 역할
                    'css-loader',	// 먼저 해석됨. js에서 css에서 인식하도록 해석
                    'postcss-loader',	// 공급업체 접두사 적용
                    'sass-loader'	// 가장 먼저 해석됨. js에서 scss에서 인식하도록 해석
                ]
            },
            { 
                test:/\.js$/,
                use: [
                    'babel-loader'  //바벨이 적용될 수 있도록 설정
                ]
            },
            {
                test:/\.(png|jpe?g|gif|webp)$/,
                use: 'file-loader'
            }        
        ]
    },
    
    plugins:[
    	new HtmlPlugin({
        	template: './index.html'	// 번들링 결과물에 html을 포함시킴
        }),
        new CopyPlugin({
        	patterns: [
            	{ from: 'static' }		// 번들링 결과물에 스태틱 파일을 포함시킴
            ]
        }),
        new VueLoaderPlugin()   // 추가
    ],
    
    devServer:{
    	host: 'localhost'	// 서버 호스팅 주소
    }
}

4. index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/reset-css@5.0.1/reset.min.css">
</head>
<body>
  <div id="app"></div>
</body>
</html>

5. main.js

import { createApp } from 'vue'
import App from './App'

createApp(App).mount('#app')

6. App.vue

<template>
  <h1>{{ message }}</h1>
  <HelloWorld />
</template>

<script>
import HelloWorld from '~/components/HelloWorld'

export default {
  components:{
    HelloWorld
  },
  data() {
    return{
      message: 'Hello Vue!!!'
    }
  }
}
</script>

7. HelloWorld.vue

<template>
  <img src="~assets/background.png" alt="logo">
</template>

<style>
  img {
    width: 600px;
    border-radius: 10px;
  }
</style>

 

8. eslint 설정(.eslintrc.js)

module.exports = {
  env:{
    browser: true,
    node: true
  },
  extends:[
    // vue
    //'plugin:vue/vue3-essential',
    'plugin:vue/vue3-strongly-recommended',
    //'plugin:vue/vue3-recommended'
    //js
    'eslint:recommended'
  ],
  parserOptions:{
    parser: 'babel-eslint'
  },
  rules:[
    "vue/html-closing-bracket-newline": ["error", {
      "singleline": "never",
      "multiline": "never"
     }],
    "vue/html-self-closing": ["error", {
      "html": {
        "void": "always",
        "normal": "never",
        "component": "always"
      },
      "svg": "always",
      "math": "always"
    }],
    "vue/html-indent": ["error", "tab", {
        attribute: 1,
        baseIndent: 1,
        closeBracket: 0,
        alignAttributesVertically: true,
        ignores: [],
      }],
  ]
}

 

9. package.json

{
  "name": "11_webpack3",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "webpack-dev-server --mode development",
    "build": "webpack --mode production"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.14.3",
    "@babel/plugin-transform-runtime": "^7.14.3",
    "@babel/preset-env": "^7.14.2",
    "@vue/compiler-sfc": "^3.0.11",
    "autoprefixer": "^10.2.5",
    "babel-eslint": "^10.1.0",
    "babel-loader": "^8.2.2",
    "copy-webpack-plugin": "^8.1.1",
    "css-loader": "^5.2.5",
    "eslint": "^7.27.0",
    "eslint-plugin-vue": "^7.9.0",
    "file-loader": "^6.2.0",
    "html-webpack-plugin": "^5.3.1",
    "postcss": "^8.2.15",
    "postcss-loader": "^5.3.0",
    "sass": "^1.32.13",
    "sass-loader": "^11.1.1",
    "style-loader": "^2.0.0",
    "vue-loader": "^16.2.0",
    "vue-style-loader": "^4.1.3",
    "webpack": "^5.37.1",
    "webpack-cli": "^4.7.0",
    "webpack-dev-server": "^4.0.0-beta.3"
  },
  "browserslist": [
    "> 1%",
    "last 2 versions"
  ],
  "dependencies": {
    "vue": "^3.0.11"
  }
}
반응형
반응형

1. 프로젝트 생성

   npm init -y

 

2. 관련 package 설치

   npm i -D snowpack svelte @snowpack/plugin-svelte ( 템플릿 구성용 )

   npm i -D @snowpack/plugin-optimize ( html, js 난독화 및 최적화 )

   npm i -D @snowpack/plugin-babel  ( 바벨 )

   npm i -D @snowpack/plugin-sass  ( sass )

   npm i -D bootstrap@next ( 부트스트랩 )

   Svelt for VS Code 패키지 설치 ( 코드 하이라이트 )

 

3. package.json 파일 script 추가

"scripts": {
   "dev": "snowpack dev",
   "build": "snowpack build"
}

 

4. snowpack.config.js 파일 생성

module.exports = {
    mount: {
        public: '/',	// root 폴더
        src: '/dist'	// build된 결과 파일 위치
    },
    plugins: [
        '@snowpack/plugin-svelte',
        '@snowpack/plugin-optimize',  
        '@snowpack/plugin-babel',	//  ( 바벨 )
        '@snowpack/plugin-sass' 	//  ( Sass )
    ],
    alias: {
        '~':'./src'	// 경로 별칭
    },
    devOptions: {
        //port: 8080,
        open: 'none'	// 서버 새로 시작 시 브라우저 열기기능 막음
    }
}

5. index.html 파일에 스크립트 파일 경로 생성

<script type="module" src="/dist/main.js"></script>

6. App.svelte 파일 생성

<script>
    let name = 'hello Svelte!'
</script>

<h1>{name}</h1>

<style lang="scss">
	$color: red;
    h1 {
    	color: $color;
    }
</style>

 

 

7. main.js 파일 생성

import App from './App.svelte'

const app = new App({
	target: document.body
})

export default app

 

Build 최적화

8. optimize 패키지 설치

9. Babel 패키지 설치

10. Sass 패키지 설치

 

11. eslint 경고 제거 

반응형

+ Recent posts