2014年11月2日

JavaScript - 使用gulp來建置JS

這篇的來源,是從這裡來的,後來看到一半,發現已經有JS神人翻成中文了..Orz..但已經弄到一半,就還是留下來記錄一下…有興趣的朋友,可以直接去看中文版的,寫的很詳細也很棒喔!!

最近因為某些因素,也開始看看JS的建置方案,至於為什麼要建置,有很多的原因,舉例來說,我們希望把JS給min化,又或許是把多個JS給綑綁成一個,又或許是為了要測試;總之,近代的JS已經沒有以前那樣的單純,所以也需要個建置流程,來處理這些事情~

而除了今天要聊的gulp外,其餘還有grunt等方案,但小弟選的是gulp,其實也沒為什麼,就覺得他的設定過程滿好的XDDD,也容易理解,就選擇了gulp~

開始前,我們必須先透過npm進行安裝,不知道甚麼是npm的朋友,可以看一下這篇

裝完npm後,我們就可以透過npm install gulp -g進行第一次的全域安裝。

image

接下來,我們就進入我們要的專案底下,針對此專案,再安裝一次gulp。

image

完成之後,我們要持續的透過npm進行gulp的套件安裝;這邊有非常多的套件,因為神人那篇已經寫的很詳細了,小弟這邊就不多加敘述了。

  • gulp-uglify - 將JS醜化…( 就類似程式碼混淆器的功能 )
  • gulp-rename - 重新命名JS檔案
  • gulp-clean - 清除目標路徑
  • gulp-concat - 合併多個JS檔案
  • gulp-notify - 通知,完成後通知功能

image

接下來,我們要在專案下,建立一個gulpfile.js的檔案。

image

檔案的內容大致如下,簡單的說,前面是針對套件的載入,接下來使用pipe來讓套件一個一個的循序漸進的工作,而gulp.dest表示目標的路徑,這邊指的是當合併成main.js或是使用uglify後,要將js檔案放到哪個路徑…最後我們再使用gulp.start來啟動Task。

以下面的案例來說,我們先使用src讀取所有的js位置,並且透過concat合併成main.js並且輸出到assets/js的路徑下,接下來,我們將此檔案新建立一個.min的檔案並且混淆裡面內容後輸出到dist/assets/js底下,完成後,使用Scripts task complete來通知我們。

var gulp = require('gulp'),
    uglify = require('gulp-uglify'),
    rename = require('gulp-rename'),
    clean = require('gulp-clean'),
    concat = require('gulp-concat'),
    notify = require('gulp-notify');

gulp.task('scripts', function () {
    return gulp.src('src/scripts/**/*.js')
      .pipe(concat('main.js'))
      .pipe(gulp.dest('dist/assets/js'))
      .pipe(rename({ suffix: '.min' }))
      .pipe(uglify())
      .pipe(gulp.dest('dist/assets/js'))
      .pipe(notify({ message: 'Scripts task complete' }));
});

gulp.task('clean', function () {
    return gulp.src(['dist/assets/css', 'dist/assets/js', 'dist/assets/img'], { read: false })
      .pipe(clean());
});

// 預設任務
gulp.task('default', ['clean'], function () {
    gulp.start('scripts');
});

完成之後,我們只要進入指令模式,打入gulp,就可以呼叫建置。

image

基本上就先到這邊,其實原文的部分,還有包含CSS、瀏覽器自動更新等功能,如果迫不急待的朋友們,可以看看參考資料,那邊寫的非常詳細喔!~

未來小弟這篇,會再慢慢的補充上去。

另外,目前Visual Studio 2013已經有提供IDE的套件,至於未來的Visual Studio 2015,則會內建,未來也會持續更新。

參考資料

Visual Studio - 自動產生NuGet Package並移除舊版Package

延續前一篇,這篇要談的是一個小小的不方便與問題,畢竟人類的懶惰是沒有極限的。

如果大家有測是下來,可能就會發現如下圖的問題,是的,產生了非常多的nupkg檔案…畢竟每次增加一個版本,就會多兩個nupkg檔案如下圖;當然如果一開始少少的可能還好,但越來越多的時候,就會感覺很髒亂…,所以這篇,我們就來看看如何建立的過程中,順便移除舊版的Package。

image

那要怎麼做呢??,其實每次建立的時候,都是透過NuGet.targets來觸發動作,所以我們要增加一些小東西到NuGet.targets裡面去。

image

首先,我們要先在前面填入以下的Code,這邊我們定義了OutputPackages的內容,是nupkg的路徑。

<ItemGroup>
    <OutputPackages Include="$(TargetDir)*.nupkg" />
</ItemGroup>  

將此Code放到下圖的位置。

image

接下來,我們要定義一個CleanDependsOn的標籤,當條件成立的時候,就會執行此標籤裡面的內容。

<CleanDependsOn Condition="$(BuildPackage) == 'true'">
  $(CleanDependsOn);
  CleanPackages;
</CleanDependsOn>

我們要把此標籤放到底下的位置。

image

最後,這個內容到底要做甚麼事情哩,就是最後面這個標籤的內容了,也就是Delete Files,要刪除的檔案,就是我們一開始定義的路徑。

<Target Name="CleanPackages">
  <Delete Files="@(OutputPackages)"></Delete>
</Target>

我們把此標籤放到以下的位置。

image

完成之後,我們要使用重建的動作,如果只用建置,是沒辦法觸發的。

image

重建之後,我們就可以發現,原本的nupkg檔案全部都被砍掉了,只留下最新版喔!!

image

最後的最後,附上完整的NuGet.targets xml給大家參考~

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ItemGroup>
    <OutputPackages Include="$(TargetDir)*.nupkg" />
  </ItemGroup>  
  <PropertyGroup>
        <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">$(MSBuildProjectDirectory)\..\</SolutionDir>

        <!-- Enable the restore command to run before builds -->
        <RestorePackages Condition="  '$(RestorePackages)' == '' ">false</RestorePackages>

        <!-- Property that enables building a package from a project -->
        <BuildPackage Condition=" '$(BuildPackage)' == '' ">false</BuildPackage>

        <!-- Determines if package restore consent is required to restore packages -->
        <RequireRestoreConsent Condition=" '$(RequireRestoreConsent)' != 'false' ">true</RequireRestoreConsent>

        <!-- Download NuGet.exe if it does not already exist -->
        <DownloadNuGetExe Condition=" '$(DownloadNuGetExe)' == '' ">false</DownloadNuGetExe>
    </PropertyGroup>

    <ItemGroup Condition=" '$(PackageSources)' == '' ">
        <!-- Package sources used to restore packages. By default, registered sources under %APPDATA%\NuGet\NuGet.Config will be used -->
        <!-- The official NuGet package source (https://www.nuget.org/api/v2/) will be excluded if package sources are specified and it does not appear in the list -->
        <!--
            <PackageSource Include="https://www.nuget.org/api/v2/" />
            <PackageSource Include="https://my-nuget-source/nuget/" />
        -->
    </ItemGroup>

    <PropertyGroup Condition=" '$(OS)' == 'Windows_NT'">
        <!-- Windows specific commands -->
        <NuGetToolsPath>$([System.IO.Path]::Combine($(SolutionDir), ".nuget"))</NuGetToolsPath>
    </PropertyGroup>

    <PropertyGroup Condition=" '$(OS)' != 'Windows_NT'">
        <!-- We need to launch nuget.exe with the mono command if we're not on windows -->
        <NuGetToolsPath>$(SolutionDir).nuget</NuGetToolsPath>
    </PropertyGroup>

    <PropertyGroup>
        <PackagesProjectConfig Condition=" '$(OS)' == 'Windows_NT'">$(MSBuildProjectDirectory)\packages.$(MSBuildProjectName.Replace(' ', '_')).config</PackagesProjectConfig>
        <PackagesProjectConfig Condition=" '$(OS)' != 'Windows_NT'">$(MSBuildProjectDirectory)\packages.$(MSBuildProjectName).config</PackagesProjectConfig>
    </PropertyGroup>

    <PropertyGroup>
      <PackagesConfig Condition="Exists('$(MSBuildProjectDirectory)\packages.config')">$(MSBuildProjectDirectory)\packages.config</PackagesConfig>
      <PackagesConfig Condition="Exists('$(PackagesProjectConfig)')">$(PackagesProjectConfig)</PackagesConfig>
    </PropertyGroup>
    
    <PropertyGroup>
        <!-- NuGet command -->
        <NuGetExePath Condition=" '$(NuGetExePath)' == '' ">$(NuGetToolsPath)\NuGet.exe</NuGetExePath>
        <PackageSources Condition=" $(PackageSources) == '' ">@(PackageSource)</PackageSources>

        <NuGetCommand Condition=" '$(OS)' == 'Windows_NT'">"$(NuGetExePath)"</NuGetCommand>
        <NuGetCommand Condition=" '$(OS)' != 'Windows_NT' ">mono --runtime=v4.0.30319 "$(NuGetExePath)"</NuGetCommand>

        <PackageOutputDir Condition="$(PackageOutputDir) == ''">$(TargetDir.Trim('\\'))</PackageOutputDir>

        <RequireConsentSwitch Condition=" $(RequireRestoreConsent) == 'true' ">-RequireConsent</RequireConsentSwitch>
        <NonInteractiveSwitch Condition=" '$(VisualStudioVersion)' != '' AND '$(OS)' == 'Windows_NT' ">-NonInteractive</NonInteractiveSwitch>

        <PaddedSolutionDir Condition=" '$(OS)' == 'Windows_NT'">"$(SolutionDir) "</PaddedSolutionDir>
        <PaddedSolutionDir Condition=" '$(OS)' != 'Windows_NT' ">"$(SolutionDir)"</PaddedSolutionDir>

        <!-- Commands -->
        <RestoreCommand>$(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)"  $(NonInteractiveSwitch) $(RequireConsentSwitch) -solutionDir $(PaddedSolutionDir)</RestoreCommand>
        <BuildCommand>$(NuGetCommand) pack "$(ProjectPath)" -Properties "Configuration=$(Configuration);Platform=$(Platform)" $(NonInteractiveSwitch) -OutputDirectory "$(PackageOutputDir)" -symbols</BuildCommand>

        <!-- We need to ensure packages are restored prior to assembly resolve -->
        <BuildDependsOn Condition="$(RestorePackages) == 'true'">
            RestorePackages;
            $(BuildDependsOn);
        </BuildDependsOn>

        <!-- Make the build depend on restore packages -->
        <BuildDependsOn Condition="$(BuildPackage) == 'true'">
            $(BuildDependsOn);
            BuildPackage;
        </BuildDependsOn>
      
        <CleanDependsOn Condition="$(BuildPackage) == 'true'">
          $(CleanDependsOn);
          CleanPackages;
        </CleanDependsOn>
    </PropertyGroup>

    <Target Name="CheckPrerequisites">
        <!-- Raise an error if we're unable to locate nuget.exe  -->
        <Error Condition="'$(DownloadNuGetExe)' != 'true' AND !Exists('$(NuGetExePath)')" Text="Unable to locate '$(NuGetExePath)'" />
        <!--
        Take advantage of MsBuild's build dependency tracking to make sure that we only ever download nuget.exe once.
        This effectively acts as a lock that makes sure that the download operation will only happen once and all
        parallel builds will have to wait for it to complete.
        -->
        <MsBuild Targets="_DownloadNuGet" Projects="$(MSBuildThisFileFullPath)" Properties="Configuration=NOT_IMPORTANT;DownloadNuGetExe=$(DownloadNuGetExe)" />
    </Target>

    <Target Name="_DownloadNuGet">
        <DownloadNuGet OutputFilename="$(NuGetExePath)" Condition=" '$(DownloadNuGetExe)' == 'true' AND !Exists('$(NuGetExePath)')" />
    </Target>

    <Target Name="RestorePackages" DependsOnTargets="CheckPrerequisites">        
        <Exec Command="$(RestoreCommand)"
              Condition="'$(OS)' != 'Windows_NT' And Exists('$(PackagesConfig)')" />

        <Exec Command="$(RestoreCommand)"
              LogStandardErrorAsError="true"
              Condition="'$(OS)' == 'Windows_NT' And Exists('$(PackagesConfig)')" />
    </Target>

    <Target Name="BuildPackage" DependsOnTargets="CheckPrerequisites">
        <Exec Command="$(BuildCommand)"
              Condition=" '$(OS)' != 'Windows_NT' " />

        <Exec Command="$(BuildCommand)"
              LogStandardErrorAsError="true"
              Condition=" '$(OS)' == 'Windows_NT' " />
    </Target>

    <Target Name="CleanPackages">
      <Delete Files="@(OutputPackages)"></Delete>
    </Target>

    <UsingTask TaskName="DownloadNuGet" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
        <ParameterGroup>
            <OutputFilename ParameterType="System.String" Required="true" />
        </ParameterGroup>
        <Task>
            <Reference Include="System.Core" />
            <Using Namespace="System" />
            <Using Namespace="System.IO" />
            <Using Namespace="System.Net" />
            <Using Namespace="Microsoft.Build.Framework" />
            <Using Namespace="Microsoft.Build.Utilities" />
            <Code Type="Fragment" Language="cs">
                <![CDATA[
                try {
                    OutputFilename = Path.GetFullPath(OutputFilename);

                    Log.LogMessage("Downloading latest version of NuGet.exe...");
                    WebClient webClient = new WebClient();
                    webClient.DownloadFile("https://www.nuget.org/nuget.exe", OutputFilename);

                    return true;
                }
                catch (Exception ex) {
                    Log.LogErrorFromException(ex);
                    return false;
                }
            ]]>
            </Code>
        </Task>
    </UsingTask>
</Project>

基本上,到這邊,就完成嚕!!

參考資料

Visual Studio - 使用Nuspec來自動產生NuGet Package

前一篇,我們使用MSBuild來達到Build的時候,自動產生NuGet Package,而當我們使用NuGet Package Explorer打開來看的時候,我們會發現Package metadata的資料也順便的幫我們填上了。

image

為什麼NuGet Package會知道填甚麼值呢?,那是因為它會自動抓AssemblyInfo.cs檔案,如下圖。

image

其中的對應表如下圖,我們可以改以下的對應表來產生不同內容的Package metadata,但要特別注意,AssemblyCulture必須保持為空值,不然會NuGet Package的產生,會有問題。

image

但實際上,AssemblyInfo還是不能涵蓋所有的Package metadata,更理想的狀況還是透過nuspec這個檔案來定義Package metadata;但如果自己要Key nuspec這個檔案也太辛苦了,所以我們可以從nupkg這個檔案,來解壓縮,找到nuspec。

( nupkg其實就是一個zip檔案,大家可以透過7-zip工具,或是直接把nupkg這個副檔名改成zip,就可以順利解壓縮了… )

image

之後,我們只需要把nuspec拷貝到專案的目錄底下去就可以了,但要特別注意,nuspec的檔案名稱,必須和專案的名稱相同喔!!

image

完成之後,重build一下,我們就可以發現,這次完全使用nuspec的內容了。

image

最後,要特別注意,因為我們希望建置NuGet Package的時候,他自動去抓取packages.config這個檔案,來解析相依的問題,所以別忘記把nuspec裡面的相依給移除掉喔。

image

也就是這行裡面的內容。

<dependencies>
  <dependency id="Newtonsoft.Json" version="6.0.6" />
</dependencies>

到這邊,就完成嚕!!~

參考資料

Visual Studio - 建置的時候自動產生NuGet Package

最近因為在寫一些Framwork給公司使用,雖然透過DLL直接給大家用也很方便,但畢竟這個時代,已經是NuGet的時代了,所以理想的情況下,還是放在NuGet上,比較方便 ( 當然,給公司用的Framework是自行架立的NuGet私服… )

而建立NuGet Package的過程中,我們可以透過NuGet的指令,或是NuGet Package Exploer,但NuGet指令要準備一堆東西…而NuGet Package Exploer則會遇到相依信套件要自己塞的窘境…而最最最理想的就是透過MSBuild的過程中,自動產生NuGet Package;這樣才符合懶人Sky的形象~~

所以去網路上找了一下,有些人有寫了一些套件,有些人有準備了一些專案,但還是看到這篇最符合小弟…( 原文連結在底下… ),所以就在這邊敘述一下過程。

首先,必須先啟用NuGet套件還原,是的,大家沒看錯,就是套件還原;這邊會使用套件還原的原因是因為,我們透過套件還原這個功能,可以幫我們把參數都給設定好,所以幾乎都不用做甚麼事情了… ( 懶人指數 + 1 )。

image

而套件還原功能開啟之後,還必須先隨便的使用NuGet下載任一套件,並且編輯,小弟這邊下載的是Json.Net。

為什麼要隨便下載一個套件勒!?,原因很簡單,因為你隨便下載一個套件之後,並且重Build,他才會自動的在專案檔下加入必要的參數。

加入的項目如下,基本上裡面的參數都是和套件還原有關的。

image

如下圖,我們可以看到,預設情況下,幫我們增加了NuGet的相關參數。

image

而實際上,到上面為止,都是處於套件還原的功能,所以我們要在專案檔這邊,加入BuildPackage的標籤,並且改為true;沒錯,就是這樣而已,這樣就可以了!!

image

這邊是原始的專案xml,有興趣的大家可以自由貼上。

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
  <PropertyGroup>
    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
    <ProjectGuid>{42ED14CC-0FAD-452C-966B-B484FE6BEDC2}</ProjectGuid>
    <OutputType>Library</OutputType>
    <AppDesignerFolder>Properties</AppDesignerFolder>
    <RootNamespace>ClassLibrary2</RootNamespace>
    <AssemblyName>ClassLibrary2</AssemblyName>
    <TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
    <FileAlignment>512</FileAlignment>
    <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
    <RestorePackages>true</RestorePackages>
    <BuildPackage>true</BuildPackage>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <DebugSymbols>true</DebugSymbols>
    <DebugType>full</DebugType>
    <Optimize>false</Optimize>
    <OutputPath>bin\Debug\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
    <DebugType>pdbonly</DebugType>
    <Optimize>true</Optimize>
    <OutputPath>bin\Release\</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <ItemGroup>
    <Reference Include="Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
      <SpecificVersion>False</SpecificVersion>
      <HintPath>..\packages\Newtonsoft.Json.6.0.6\lib\net45\Newtonsoft.Json.dll</HintPath>
    </Reference>
    <Reference Include="System" />
    <Reference Include="System.Core" />
    <Reference Include="System.Xml.Linq" />
    <Reference Include="System.Data.DataSetExtensions" />
    <Reference Include="Microsoft.CSharp" />
    <Reference Include="System.Data" />
    <Reference Include="System.Xml" />
  </ItemGroup>
  <ItemGroup>
    <Compile Include="Class1.cs" />
    <Compile Include="Properties\AssemblyInfo.cs" />
  </ItemGroup>
  <ItemGroup>
    <None Include="packages.config" />
  </ItemGroup>
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
  <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
    <PropertyGroup>
      <ErrorText>此專案參照此電腦中缺少的  NuGet 套件。啟用「NuGet 套件還原」
      以便下載。如需詳細資訊,請參閱 http://go.microsoft.com/fwlink/?LinkID=322105。缺少的檔案為 {0}。</ErrorText>
    </PropertyGroup>
    <Error Condition="!Exists('$(SolutionDir)\.nuget\NuGet.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\.nuget\NuGet.targets'))" />
  </Target>
  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
       Other similar extension points exist, see Microsoft.Common.targets.
  <Target Name="BeforeBuild">
  </Target>
  <Target Name="AfterBuild">
  </Target>
  -->
</Project>

當加入那行之後,我們重新Build後,就可以在bin底下的debug看到nupkg的檔案產生了!!其中symbols是提供偵錯用途的,裡面包含了source喔~

image

第一步驟就到這邊,就是那麼簡單~~

參考資料