Coder Social home page Coder Social logo

slick-for-svelte's Introduction

Slick For Svelte

What does this library do for you.

Manage your views and routing following a practical and easy to follow approach for your svelte components.

@Controller("/user/")
export class UserController {
  
  
  
  @View("/home", HomePage)
  async homepage(userapi: GithubApi, @Query("page") page = 1) {
    let users = await userapi.getPage(page);

    return {
      users: users
    };
  }

  
  
  @View("/:username", UserPage)
  async getUserPage(api: GithubApi, @Param("username") username: string) {
    let user = await api.getUserByName(username);
    return {
      user
    };
  }

}

This library is inspired by nest and uses dependency injection to gather all the requirements needed to call a route.

Installation

Before I install I just want to play with the framework and see it work.

Click Here

I rather have is all done for me, Where do I clone.

Here

I want to set up it up from the official svelte repo, it's gonna be more work, but I am up for it. (Proceed)

Starting from the Official repo. Notice I am using the WebPack template and not the roll up template.

npx degit sveltejs/template-webpack svelte-app
cd svelte-app
yarn or npm install

Dependencies (YOOOU need to install them, not me)

  • typescript
  • reflect-metadata
  • @slick-for/svelte
  • history

Confused? Paste one of the following options in your terminal inside your project folder.

npm install reflect-metadata @slick-for/svelte history
npm install --save-dev typescript

or

yarn add reflect-metadata @slick-for/svelte history
yarn add --dev typescript 

Confused? Do number 1

You need Typescript (Not a nice to have, Required)

I used the Webpack Template, Did you use Rollup? You will need to investigate how to get typescript working with Rollup.

Here is what you do for webpack, something similar is needed for rollup.

npm install --save-dev awesome-typescript-loader
yarn add --dev awesome-typescript-loader

Located webpack.config.js in your project root.

Here is what you need to do Exactly

  1. Easy Way
Copy and Paste the config here 
https://github.com/shavyg2/slick-for-svelte-test/blob/master/webpack.config.js
  1. Non Beginner (Webpack Config scares some people.)

The original file contains the following:

resolve: {
    alias: {
        svelte: path.resolve('node_modules', 'svelte')
    },
    extensions: ['.mjs', '.js', '.svelte'],
    mainFields: ['svelte', 'browser', 'module', 'main']
},

You need to add support for typescript extension (typescript extension added)

resolve: {
    alias: {
        svelte: path.resolve('node_modules', 'svelte')
    },
    extensions: ['.mjs', '.js', '.svelte','.ts'], //here
    mainFields: ['svelte', 'browser', 'module', 'main']
},

Add awesome-typescript-loader and configure it for webpack, below is a reference.

{
    test: /\.ts$/,
    use: {
        loader: 'awesome-typescript-loader',
        options: {
            transpileOnly:true //make life easy
        }
    }
},

Configure webpack dev server to use the index.html file for your single page application. Below is a reference.

devServer: {
    port:process.env.PORT,
    historyApiFallback: {
        index: 'index.html'
    }
},

The entry file webpack compiles need to now be a typescript file.

entry: {
    bundle: ['./src/main.ts']
},

Typescript config

Generate tsconfig file

npx tsc --init

Add the following to your tsconfig.json

    "experimentalDecorators": true,        
    "emitDecoratorMetadata": true, 

I use these settings. It works lovely for me tsconfig.json

    "target": "es5",                         
    "module": "commonjs",                    
    "lib": ["dom","esnext"],    

Confused??? Click this and copy paste to tsconfig.json

Templates

Honestly just copy this and place under src/Template.svelte. You can check this template file out to understand how certain things work, however you don't need to understand it.

<script>
    export let layout;
    export let NotFound;
    export let viewProps;
    export let loading;
    export let error;

</script>
{#await viewProps}
    {#if layout}
            <svelte:component this={layout}>
                <svelte:component this={loading}/>
            </svelte:component>
        {:else}
            <svelte:component this={loading}/>
    {/if}
{:then props}
    {#if viewProps.NotFound}
        <svelte:component this={props.NotFound}  />
    {:else}
        {#if props.layout||layout}
            <svelte:component this={props.layout || layout} {...props}>
                <svelte:component this={view} {...props}/>
            </svelte:component>
        {:else}
            <svelte:component this={view} {...props}/>
        {/if}
    {/if}
{:catch error$}
    <svelte:component this={error}  error={error$}/>
{/await}

This is the global template engine for all views. You shouldn't need to change it unless it is to add something application wide. Please refrain from doing this. There are better places to add customizations.

404 Page Sample 404 or Create your own.

<!-- src/404.svelte -->
<h1>Error 404</h1>
Not found

Imports

    import {Controller,View,Module,Injectable,Inject,Param,Query,History} from "@slick-for/svelte";

These are some of the tools that come with this library and are very common to see/use.

Main.ts

import {Module,SlickForSvelteFactory} from "@slick-for/svelte";
import {createBrowserHistory} from "history"
import { UserController } from "./controller/UserController";
import { GithubApi } from "./services/github-api";
import Template from "./Template.svelte";
import Error404 from "./404.svelte"
import ErrorPage from "./Error.svelte";


const history =  createBrowserHistory();
@Module({
	controllers:[UserController],
	provider:[GithubApi]
})
export class ApplicationModule{

}
const app = SlickForSvelteFactory.create(ApplicationModule,{
	base:Template, // Remember that template i told you to keep in your back pocket, take it out.
	history, //https://www.npmjs.com/package/history
	component404:Error404, //svelte 404 Page, make it up or copy from somewhere else,
	error:ErrorPage,
	target:document.body //Where to render to https://svelte.dev/docs#Creating_a_component
})

app.Initialize();

This will start your application listen for the URL changes using the history API.

Wheeeew (AMAZING!!!!!)

Good Job, you have done great so far. Stretch you legs. Lemme know when you are ready. I will literally be waiting for you.

Controller (Basic)

Add the missing controller file.

//src/controllers/UserController.ts
import { Controller, View, Param, Query } from "@slick-for/svelte";


//Regular svelte components. Confused ? https://svelte.dev/tutorial/basics
import HomePage from "./pages/home.svelte";
import UserPage from "./pages/user.svelte";


//Service Component
import { GithubApi } from "../services/github-api";

@Controller("/user/")
export class UserController {
  @View("/home", HomePage)
  async homepage(userapi: GithubApi, @Query("page") page = 1) {
    let users = await userapi.getPage(page);

    return {
      users: users
    };
  }

  @View("/:username", UserPage)
  async getUserPage(api: GithubApi, @Param("username") username: string) {
    let user = await api.getUserByName(username);
    return {
      user
    };
  }
}

Services

The case of the missing service file. You can add it. Note that services can't have method properties automatically injected. This can only be done in the controller. However the constructor can.

import {
  Injectable
} from "@slick-for/svelte";

@Injectable()
export class GithubApi {
  private apiUrl = "https://api.github.com";

  async getPage(page: number) {
    let res = await fetch(`${this.apiUrl}/users?since=${page}`);
    let users = await this.isGood(res);
    return users;
  }

  async getUserByName(username: string) {
    let res = await fetch(`${this.apiUrl}/users/${username}`);
    let users = await this.isGood(res);
    return users;
  }

  private async isGood(res: Response) {
    if (res.status - 299 > 0) {
      let text = await res.text();
      let parsed;
      try {
        parsed = JSON.parse(text);
      } catch (e) {
        throw text;
      }

      throw parsed;
    } else {
      return res.json();
    }
  }
}

Serve over http and see the result

yarn run dev

You should be seeing the 404 page now

Need Syntax highlighting for Svelte

https://marketplace.visualstudio.com/items?itemName=JamesBirtles.svelte-vscode

Advanced (wow super star. Keep going!!)

Controller (Advance)

{
    layout:SvelteComponent
    loading:SvelteComponent
    error:SvelteComponent
    pause:400

}

layout

This is a custom view that you should give you a page, it allows you to customize the layout for that controller. Make sure that you have a slot in that layout. This is where your view from your page will go.

loading

This is the layout that will be shown while your page your data is loading. It will use the layout while it is loading.

error

If an error happens this is the component that will be shown, it will receieve one prop and it will be called error.

pause

When you are using a single page application the layout will be able to load much faster than the data will, This will delay the transition until the data is ready. However if the data is taking a really long time (over 400ms) by default then it will switch to that page and display the loading, component.

This is useful because you don't want user on really fast connection to be seeing this loading screen if it takes less than 400 ms for the data to load. If you want to increase or decrease this time however you can. The default 400 ms is good however.

@Controller("/admin",{
    layout:AdminComponent,
    loading:AdminLoadingView,
    error:AdminErrorView,
    pause:400
})
class AdminController{

    constructor(private user:UserService,private account:AccountService){

    }

    @View("/",AdminMainComponent)
    async getMainPage(){

        //This can be using writable stores in the background.
        const user = await this.user.getAdminUserDetails();
        

        //Notice that i didn't await for the settings and the Admin panel details
        const userSettings = this.account.getUserSettings(user.id);
        const userPanels = this.account.getAdminPanelDetails(user.id)

        return {
            user,
            userSettings,
            userPanels,
        }
    }
}
<!-- AdminMainComponent -->
<div>
    Welcome, {user.username}
</div>
<div class="content">
    {#await userPanels as panels}
        <div class="panels">
            {#each panels as panel}
                <!-- don't actually name your clases as such -->
                <div class="make-it-sexy">
                    <PanelComponent {panel}/>
                </div>
            {/each}
        </div>

    {:catch error}
        <div>Failed to load panels</div>
    {/await}

    {#await userSettings as settings}
    <!-- Please don't name classes as such -->
    <div class="make-it-spacy">
        <SettingsView {settings}/>
    </div>
    {:catch error}
        <div>Settings failed to load</div>
    {/await}
</div>

Back to the method

    ...

    @View("/",AdminMainComponent)
    async getMainPage(){

        //This can be using writable stores in the background.
        const user = await this.user.getAdminUserDetails();
        

        //Notice that i didn't await for the settings and the Admin panel details
        const userSettings = this.account.getUserSettings(user.id);
        const userPanels = this.account.getAdminPanelDetails(user.id)

        return {
            user,
            userSettings,
            userPanels,
        }
    }

    ...

The reason why i waited for the view to load is that i don't want the page to be shown until that date is ready. This will keep the page until the pause amount is met, at which point this will show the loading page.

However if the data is resolved in a decent enough time then it will show your page. There are somethings however we don't mind waiting for and we can start rendering the page.

We will wait for the user panels and the user settings and can show a loading or a grayout version while the user is waiting for the page to load.

This will make your page feel more responsive, You could have also loaded all of the information inside the controller method. This is completely upto you want you want to wait for in the component and what you want your page to load with.

This saves you from having to do alot of templating in the the view layer since it's best for rendering views.

but for the things that you would like to wait for you have that option.

Injectable

Injectable allows you to manage your application in a way where the classes and set up code just works and you don't have to string them all together. The Library will do all of the heavy lifting but does provide you with enough options so that you can get some things dont yourself.

Providers

Providers or Injectables come in different shapes and sizes

    //Singleton
   @Injectable()
    class Repo{

    }

    // a new service is created for each request or page navigation
    @Injectable({
        scope:"Request"
    })
    class Service{

    }

    /**
     * Since this depends on something that is refreshed for each navigation it will not default to being built with every request, you can't change this.
     */ 
    @Injectable()
    class Operation{
        constructor(
            public service:Service,
            @Inject("simple") public simple,
            public repo:Repo
            ){}
    }


    //This controller will be new everytime a user goes to a different page or reloads.
    @Controller("/thing",{
        scope:"Request",
    })
    class TestController{

    }

    //Need to inject some text use the @Inject Parameter decorator
    const simpleProvider = {
        provide:"simple",
        useValue:"Simple"
    }

    /**
     *  Need something resolved async but you still want the actual object. Use a factory.
     *  Notice the inject is used.
     * 
     * Transient a fresh copy for every page load.
     * 
     */
    const factory = {
        provide:"factory",
        async useFactory(operation:Operation){

                await new Promise((r)=>{
                    setTimeout(r,500)
                })

                return {
                    operation
                }
        },
        inject:[Operation],
        scope:"Transient" 
    }

slick-for-svelte's People

Contributors

shavyg2 avatar

Stargazers

Marcus Feitoza avatar Antônio Vieira avatar Andy Chrzaszcz avatar  avatar Joel Mukuthu avatar Karthy Mallichetty avatar Anonimus Programus avatar Marcus avatar Michael Floering avatar  avatar Valentyn avatar Daniel Piedra Quintero avatar Nikolaus Schlemm avatar Jacob avatar Raphael Polniak avatar Zafar Ansari avatar  avatar Dmitri Don avatar joshua Oguche avatar  avatar =^..^= avatar Djamel Benali avatar  avatar Alexsey Ramzaev avatar

Forkers

andy9775

slick-for-svelte's Issues

Implement path prefix and base url

Base url on module would be nice as a user wouldn't have to keep including it in the controllers.

This should be added to the module decorator

target and module in tsconfig

I found that this works "ONLY" if target is set to es5 and module to commonjs in tsconfig. when i try esnext for module or target (or any other combination) , i only get the 404 page . why is that ?

LayoutProps not exported by factory

I am trying to add this lib to the official svelte starter and and get this error

'LayoutProps' is not exported by node_modules/@slick-for/svelte/dist/src/slick-for-svelte-factory.js

Even though decorators are imported in slick-for-svelte-factory file. Any idea why this is happening ?

Global state object

Using a @State('key') would be nice for managing global state objects. This would give users something nice rather than having multiple state objects all over the file system. The framework would just provide the state object to the user.

Similar to how use state in react just works

Overwrite layout on a specific view

The ability to override specific view layout would be nice. This shouldn't be used frequently as similar things should have for the most part very similar layouts, but once in a while there might be a reason to give one view a layout different from the rest of the views in the controller.

Default Providers Param and Query

There are a couple of things that you should be able to inject without having to do too much work.
Things such as parameters amd query object. This should be available by default.

import {Param} from "@slick-for/svelte"



//method

@View("/:username")
showUserPage(@Param('username') username:string){

}
import {Param} from "@slick-for/svelte"



//method

@View("/:username")
showUserPage(@Param() param){
   let username = param.username
}

The same thing is need for the query string

import {Query} from "@slick-for/svelte"



//method url : /user/bill?view=admin

@View("/:username")
showUserPage(@Query('username') username:string,@Query() query){

}

Good default Page state

This should make it easy for people to store a global state for the current page that is loaded.
must be unique for each request that it gets

import {State} from "@slick-for/svelte"



//method

@View("/:username")
showUserPage(@State() writableStore){
   T//
}```

Layout Method

The idea of a layout method is that once you have a layout and you need to supply props to it, its pretty annoying to have to get that prop multiple times, if you are able to just tag a method so that the layout knows where to get the layout props, that would be awesome.

class Main{
    @LayoutProps
    propsNeededForLayout(){
          return {...props}
    }
}

I would never have to call a method more than once.
If someone tried to use it more than once on a controller it would throw an error.

History Provider

import {History} from "@slick-for/svelte"



//method

@View("/:username")
showUserPage(@History() history){
  history.push("/forbidden")
}

Type: Read me

Typo in the documentation
give some incite as
should be insight

using sass

Hi @shavyg2 how do i modify the webpack config in the starter project to enable me use sass/scss style in .svelte files and globally in main.ts instead of css

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.